Repository files navigation

context-packer

Budget-aware, diversity-maximizing chunk packing for LLM context windows.

npm versionnpm downloadslicensenode


Description

context-packer selects and arranges the optimal subset of retrieved chunks to fit within a fixed token budget. Every RAG pipeline must decide which chunks to include, how many tokens they consume, and in what order to place them. This package solves all three problems in a single API call.

The library provides multiple selection strategies (greedy, MMR, knapsack, custom), redundancy deduplication via configurable similarity thresholds, and positional reordering to counter the "lost-in-the-middle" effect documented by Liu et al. (2023). Every call returns a structured PackReport explaining exactly which chunks were selected or excluded and why.

Zero runtime dependencies. Written in TypeScript with full type exports.


Installation

npm install context-packer

Requires Node.js 18 or later.


Quick Start

import{pack}from'context-packer'importtype{ScoredChunk}from'context-packer'constchunks: ScoredChunk[]=[{content: 'Relevant document about authentication',score: 0.92,tokens: 50},{content: 'Relevant document about authorization',score: 0.85,tokens: 40},{content: 'Tangentially related document',score: 0.61,tokens: 80},]const{chunks: packed, report }=awaitpack(chunks,{budget: 100})console.log(report.selectedCount)// 2console.log(report.tokensUsed)// 90console.log(report.tokensRemaining)// 10console.log(report.utilization)// 0.9

Features

  • Multiple selection strategies -- Greedy, Maximal Marginal Relevance (MMR), 0/1 knapsack dynamic programming, and custom strategy support.
  • Redundancy deduplication -- Filters near-duplicate chunks before selection using trigram Jaccard similarity or cosine similarity over embedding vectors.
  • Positional reordering -- U-shaped ordering places high-relevance chunks at the beginning and end of the context, countering the lost-in-the-middle effect.
  • Hard token budget enforcement -- Total token count of selected chunks never exceeds the budget, inclusive of configurable per-chunk overhead.
  • Pluggable token counter -- Ships with a character-based approximation (Math.ceil(text.length / 4)). Swap in tiktoken, gpt-tokenizer, or any other counter.
  • Structured pack reports -- Every call returns a PackReport with utilization metrics, excluded chunk reasons, timing, and strategy metadata.
  • Factory pattern -- createPacker produces a reusable packer instance with fixed configuration for repeated use across queries.
  • Zero runtime dependencies -- No production dependencies to audit or maintain.
  • Full TypeScript support -- Ships with declaration files and source maps.

API Reference

pack(chunks, options)

Selects and orders the best subset of chunks that fit within the token budget.

functionpack(chunks: ScoredChunk[],options: PackOptions): Promise<PackResult>

Parameters:

ParameterTypeDescription
chunksScoredChunk[]Array of retrieved chunks with relevance scores.
optionsPackOptionsPacking configuration including budget, strategy, and ordering.

Returns:Promise<PackResult> containing the selected chunks array and a report.

Example:

constresult=awaitpack(chunks,{budget: 4000,strategy: 'mmr',lambda: 0.7,ordering: 'u-shaped',redundancyThreshold: 0.85,})

createPacker(config)

Creates a reusable packer instance with fixed configuration.

functioncreatePacker(config: PackOptions): {pack: (chunks: ScoredChunk[])=>Promise<PackResult>}

Parameters:

ParameterTypeDescription
configPackOptionsFixed packing configuration applied to every call.

Returns: An object with a pack method that accepts only a ScoredChunk[] array.

Example:

constpacker=createPacker({budget: 4000,strategy: 'mmr',lambda: 0.7})constresult1=awaitpacker.pack(chunksFromQuery1)constresult2=awaitpacker.pack(chunksFromQuery2)

PackError

Custom error class thrown for invalid configurations.

classPackErrorextendsError{readonlycode: stringreadonlydetails?: Record<string,unknown>}

Error codes:

CodeCondition
INVALID_BUDGETbudget is zero or negative.
MISSING_CUSTOM_STRATEGYstrategy is 'custom' but no customStrategy function was provided.
INVALID_STRATEGYAn unknown strategy value was provided.
DIMENSION_MISMATCHEmbedding vectors have different dimensions in cosine similarity.
MISSING_EMBEDDINGSsimilarityMetric is 'cosine' but one or both chunks lack embeddings.

ScoredChunk

Input chunk interface representing a retrieved document segment.

interfaceScoredChunk{content: string// The chunk textscore: number// Relevance score (typically 0-1, higher is better)id?: string// Unique identifier (auto-generated as "chunk-N" if omitted)tokens?: number// Pre-computed token count (skips token counting if provided)embedding?: number[]// Embedding vector (enables cosine similarity for MMR/dedup)metadata?: Record<string,unknown>// Arbitrary metadata (e.g., sourceId, timestamp, url)}

PackedChunk

Output chunk interface for each selected chunk in the result.

interfacePackedChunk{id: string// Chunk identifiercontent: string// The chunk textscore: number// Original relevance scoretokens: number// Token countposition: number// Zero-based position in the ordered outputmetadata?: Record<string,unknown>// Preserved metadata from the input chunk}

ExcludedChunk

Describes a chunk that was not selected, along with the reason for exclusion.

interfaceExcludedChunk{id: stringcontent: stringscore: numbertokens: numberreason: 'budget'|'redundant'|'strategy'|'max-candidates'redundantWith?: string// ID of the chunk this was redundant withsimilarity?: number// Similarity score that triggered redundancy exclusionmetadata?: Record<string,unknown>}

Exclusion reasons:

ReasonDescription
budgetChunk did not fit within the remaining token budget.
redundantChunk exceeded the similarity threshold compared to a higher-scored chunk.
strategyChunk was excluded by the selection strategy.
max-candidatesChunk was beyond the maxCandidates cutoff.

PackOptions

Full configuration interface for pack() and createPacker().

interfacePackOptions{budget: numberstrategy?: 'greedy'|'mmr'|'knapsack'|'custom'lambda?: numberordering?: 'natural'|'u-shaped'|'chronological'redundancyThreshold?: numbersimilarityMetric?: 'auto'|'cosine'|'jaccard'chunkOverheadTokens?: numbertokenCounter?: (text: string)=>numbermaxCandidates?: numbercustomStrategy?: (chunks: ScoredChunk[],ctx: StrategyContext)=>ScoredChunk[]}
OptionTypeDefaultDescription
budgetnumberrequiredMaximum total tokens for the packed context.
strategystring'greedy'Selection strategy. One of 'greedy', 'mmr', 'knapsack', 'custom'.
lambdanumber0.5MMR trade-off parameter. 1.0 = pure relevance, 0.0 = pure diversity.
orderingstring'natural'Output ordering strategy. One of 'natural', 'u-shaped', 'chronological'.
redundancyThresholdnumberundefinedSimilarity threshold for deduplication. Chunks with similarity >= threshold are removed. Set to 1.0 or omit to disable.
similarityMetricstring'auto'Similarity function. 'auto' uses cosine when embeddings are present, Jaccard otherwise.
chunkOverheadTokensnumber0Extra tokens charged per chunk (separators, citation markers, etc.).
tokenCounterfunctionMath.ceil(text.length / 4)Custom token counting function.
maxCandidatesnumberundefinedLimit the number of input chunks considered. Excess chunks are excluded with reason 'max-candidates'.
customStrategyfunctionundefinedRequired when strategy is 'custom'. Receives candidates and a StrategyContext, returns selected chunks.

StrategyContext

Context object passed to custom strategy functions.

interfaceStrategyContext{budget: number// Token budgetchunkOverheadTokens: number// Per-chunk overheadcountTokens: (text: string)=>number// Active token counter functionoptions: PackOptions// Full options object}

PackReport

Structured report returned with every pack result.

interfacePackReport{tokensUsed: number// Total tokens consumed by selected chunks (including overhead)budget: number// The token budget that was providedtokensRemaining: number// budget - tokensUsedutilization: number// tokensUsed / budget (range 0-1)selectedCount: number// Number of chunks selectedexcludedCount: number// Number of chunks excludedstrategy: string// Strategy that was usedordering: string// Ordering that was appliedexcluded: ExcludedChunk[]// Details on every excluded chunktimestamp: string// ISO 8601 timestamp of the pack operationdurationMs: number// Wall-clock duration in milliseconds}

PackResult

Top-level return type from pack().

interfacePackResult{chunks: PackedChunk[]// Selected and ordered chunksreport: PackReport// Structured packing report}

Configuration

Selection Strategies

Greedy (default)

Sorts chunks by score descending and selects greedily until the budget is full. Time complexity: O(n log n).

constresult=awaitpack(chunks,{budget: 4000})// or explicitly:constresult=awaitpack(chunks,{budget: 4000,strategy: 'greedy'})

MMR (Maximal Marginal Relevance)

Iteratively selects the chunk that maximizes lambda * relevance - (1 - lambda) * maxSimilarityToSelected. Balances relevance and diversity.

constresult=awaitpack(chunks,{budget: 4000,strategy: 'mmr',lambda: 0.7,// 1.0 = pure relevance (greedy-like), 0.0 = pure diversity})

When embeddings are provided on ScoredChunk.embedding, MMR uses cosine similarity for diversity computation. Otherwise it falls back to trigram Jaccard similarity.

Knapsack (0/1 Dynamic Programming)

Solves the 0/1 knapsack problem to maximize total score within the exact token budget. Finds the globally optimal subset, not just the greedy-best.

constresult=awaitpack(chunks,{budget: 4000,strategy: 'knapsack',})

For budgets exceeding 5,000 tokens, the knapsack strategy automatically falls back to greedy to avoid excessive memory and computation costs.

Custom Strategy

Supply your own selection function.

constresult=awaitpack(chunks,{budget: 4000,strategy: 'custom',customStrategy: (candidates,ctx)=>{// Select only high-confidence chunksreturncandidates.filter(c=>c.score>0.8)},})

The customStrategy function receives the full candidate list (after redundancy filtering) and a StrategyContext. It must return the subset of ScoredChunk objects to include.

Ordering Strategies

Natural (default)

Chunks are output in the order determined by the selection strategy (score descending for greedy).

U-Shaped

Places the highest-relevance chunks at the beginning and end of the context, with lower-relevance chunks in the middle. This counters the "lost-in-the-middle" effect where LLMs underweight information in the middle of long contexts.

constresult=awaitpack(chunks,{budget: 4000,ordering: 'u-shaped'})

Chronological

Sorts chunks by metadata.timestamp ascending. Useful for time-sensitive contexts where temporal order matters.

constresult=awaitpack(chunks,{budget: 4000,ordering: 'chronological'})

Requires metadata.timestamp (numeric) on each chunk.

Token Counting

The built-in token counter uses Math.ceil(text.length / 4) as a fast approximation suitable for GPT-family models. For exact counts, provide a custom counter:

import{encode}from'gpt-tokenizer'constresult=awaitpack(chunks,{budget: 4000,tokenCounter: (text)=>encode(text).length,})

You can also pre-compute token counts by setting tokens on each ScoredChunk, which bypasses the counter entirely.

Chunk Overhead

Account for per-chunk formatting overhead (separators, citation markers, XML tags) with chunkOverheadTokens:

constresult=awaitpack(chunks,{budget: 4000,chunkOverheadTokens: 10,// 10 extra tokens per chunk for formatting})

The overhead is added to each chunk's token count during both budget calculations and report metrics.


Error Handling

context-packer throws PackError instances for invalid configurations. Each error includes a machine-readable code and an optional details object.

import{pack,PackError}from'context-packer'try{awaitpack(chunks,{budget: -1})}catch(err){if(errinstanceofPackError){console.error(err.code)// 'INVALID_BUDGET'console.error(err.message)// 'Budget must be positive'console.error(err.details)// undefined (or additional context)}}

Error Codes

CodeThrown When
INVALID_BUDGETbudget is zero or negative.
MISSING_CUSTOM_STRATEGYstrategy is 'custom' and customStrategy is not provided.
INVALID_STRATEGYAn unknown strategy value was provided.
DIMENSION_MISMATCHEmbedding vectors have different dimensions in cosine similarity.
MISSING_EMBEDDINGSsimilarityMetric is 'cosine' but one or both chunks lack embeddings.

When no chunks fit the budget, pack returns an empty chunks array with a valid PackReport rather than throwing. Check report.selectedCount === 0 to detect this case.


Advanced Usage

Redundancy Deduplication

Remove near-duplicate chunks before selection to maximize information density:

constresult=awaitpack(chunks,{budget: 4000,redundancyThreshold: 0.85,similarityMetric: 'auto',})

Chunks sorted by score descending are compared pairwise against already-confirmed chunks. If a chunk's similarity to any confirmed chunk meets or exceeds the threshold, it is excluded with reason 'redundant'. The excluded entry includes redundantWith (the ID of the similar chunk) and similarity (the computed score).

Set redundancyThreshold to 1.0 or omit it to disable deduplication.

Similarity metrics:

  • 'auto' (default) -- Uses cosine similarity when both chunks have embedding vectors, Jaccard trigram similarity otherwise.
  • 'cosine' -- Forces cosine similarity. Falls back to Jaccard if embeddings are missing.
  • 'jaccard' -- Always uses trigram Jaccard similarity over chunk text content.

Embedding-Based Diversity

For best results with MMR or redundancy filtering, provide embedding vectors:

constchunks: ScoredChunk[]=[{content: 'Document about authentication',score: 0.92,tokens: 50,embedding: [0.12,0.45,0.78,/* ... */],},{content: 'Document about authorization',score: 0.85,tokens: 40,embedding: [0.11,0.43,0.80,/* ... */],},]constresult=awaitpack(chunks,{budget: 4000,strategy: 'mmr',lambda: 0.6,redundancyThreshold: 0.9,})

Limiting Candidates

When working with large candidate sets, use maxCandidates to cap the number of chunks considered:

constresult=awaitpack(largeChunkSet,{budget: 4000,maxCandidates: 50,// Only consider the first 50 chunks})

Chunks beyond the limit are excluded with reason 'max-candidates' and appear in report.excluded.

Inspecting the Pack Report

The PackReport provides full transparency into packing decisions:

const{chunks: packed, report }=awaitpack(candidates,{budget: 4000,strategy: 'mmr',lambda: 0.7,redundancyThreshold: 0.85,})console.log(`Strategy: ${report.strategy}`)console.log(`Ordering: ${report.ordering}`)console.log(`Utilization: ${(report.utilization*100).toFixed(1)}%`)console.log(`Selected: ${report.selectedCount}, Excluded: ${report.excludedCount}`)console.log(`Duration: ${report.durationMs}ms`)for(constexofreport.excluded){console.log(` Excluded "${ex.id}": ${ex.reason}`)if(ex.reason==='redundant'){console.log(` Redundant with: ${ex.redundantWith} (similarity: ${ex.similarity})`)}}

Combining Strategies with Ordering

Pair any selection strategy with any ordering strategy:

// MMR selection with U-shaped ordering for maximum qualityconstresult=awaitpack(chunks,{budget: 4000,strategy: 'mmr',lambda: 0.6,ordering: 'u-shaped',redundancyThreshold: 0.85,chunkOverheadTokens: 5,})

TypeScript

context-packer is written in TypeScript and ships with declaration files. All public types are exported from the package entry point:

import{pack,createPacker,PackError}from'context-packer'importtype{ScoredChunk,PackedChunk,ExcludedChunk,PackOptions,StrategyContext,PackReport,PackResult,}from'context-packer'

The package targets ES2022 and compiles to CommonJS. Declaration maps are included for IDE navigation into source types.


License

MIT

About

Optimally pack retrieved chunks into an LLM context window

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

context-packer

Budget-aware, diversity-maximizing chunk packing for LLM context windows.

npm versionnpm downloadslicensenode


Description

context-packer selects and arranges the optimal subset of retrieved chunks to fit within a fixed token budget. Every RAG pipeline must decide which chunks to include, how many tokens they consume, and in what order to place them. This package solves all three problems in a single API call.

The library provides multiple selection strategies (greedy, MMR, knapsack, custom), redundancy deduplication via configurable similarity thresholds, and positional reordering to counter the "lost-in-the-middle" effect documented by Liu et al. (2023). Every call returns a structured PackReport explaining exactly which chunks were selected or excluded and why.

Zero runtime dependencies. Written in TypeScript with full type exports.


Installation

npm install context-packer

Requires Node.js 18 or later.


Quick Start

import{pack}from'context-packer'importtype{ScoredChunk}from'context-packer'constchunks: ScoredChunk[]=[{content: 'Relevant document about authentication',score: 0.92,tokens: 50},{content: 'Relevant document about authorization',score: 0.85,tokens: 40},{content: 'Tangentially related document',score: 0.61,tokens: 80},]const{chunks: packed, report }=awaitpack(chunks,{budget: 100})console.log(report.selectedCount)// 2console.log(report.tokensUsed)// 90console.log(report.tokensRemaining)// 10console.log(report.utilization)// 0.9

Features

  • Multiple selection strategies -- Greedy, Maximal Marginal Relevance (MMR), 0/1 knapsack dynamic programming, and custom strategy support.
  • Redundancy deduplication -- Filters near-duplicate chunks before selection using trigram Jaccard similarity or cosine similarity over embedding vectors.
  • Positional reordering -- U-shaped ordering places high-relevance chunks at the beginning and end of the context, countering the lost-in-the-middle effect.
  • Hard token budget enforcement -- Total token count of selected chunks never exceeds the budget, inclusive of configurable per-chunk overhead.
  • Pluggable token counter -- Ships with a character-based approximation (Math.ceil(text.length / 4)). Swap in tiktoken, gpt-tokenizer, or any other counter.
  • Structured pack reports -- Every call returns a PackReport with utilization metrics, excluded chunk reasons, timing, and strategy metadata.
  • Factory pattern -- createPacker produces a reusable packer instance with fixed configuration for repeated use across queries.
  • Zero runtime dependencies -- No production dependencies to audit or maintain.
  • Full TypeScript support -- Ships with declaration files and source maps.

API Reference

pack(chunks, options)

Selects and orders the best subset of chunks that fit within the token budget.

functionpack(chunks: ScoredChunk[],options: PackOptions): Promise<PackResult>

Parameters:

ParameterTypeDescription
chunksScoredChunk[]Array of retrieved chunks with relevance scores.
optionsPackOptionsPacking configuration including budget, strategy, and ordering.

Returns:Promise<PackResult> containing the selected chunks array and a report.

Example:

constresult=awaitpack(chunks,{budget: 4000,strategy: 'mmr',lambda: 0.7,ordering: 'u-shaped',redundancyThreshold: 0.85,})

createPacker(config)

Creates a reusable packer instance with fixed configuration.

functioncreatePacker(config: PackOptions): {pack: (chunks: ScoredChunk[])=>Promise<PackResult>}

Parameters:

ParameterTypeDescription
configPackOptionsFixed packing configuration applied to every call.

Returns: An object with a pack method that accepts only a ScoredChunk[] array.

Example:

constpacker=createPacker({budget: 4000,strategy: 'mmr',lambda: 0.7})constresult1=awaitpacker.pack(chunksFromQuery1)constresult2=awaitpacker.pack(chunksFromQuery2)

PackError

Custom error class thrown for invalid configurations.

classPackErrorextendsError{readonlycode: stringreadonlydetails?: Record<string,unknown>}

Error codes:

CodeCondition
INVALID_BUDGETbudget is zero or negative.
MISSING_CUSTOM_STRATEGYstrategy is 'custom' but no customStrategy function was provided.
INVALID_STRATEGYAn unknown strategy value was provided.
DIMENSION_MISMATCHEmbedding vectors have different dimensions in cosine similarity.
MISSING_EMBEDDINGSsimilarityMetric is 'cosine' but one or both chunks lack embeddings.

ScoredChunk

Input chunk interface representing a retrieved document segment.

interfaceScoredChunk{content: string// The chunk textscore: number// Relevance score (typically 0-1, higher is better)id?: string// Unique identifier (auto-generated as "chunk-N" if omitted)tokens?: number// Pre-computed token count (skips token counting if provided)embedding?: number[]// Embedding vector (enables cosine similarity for MMR/dedup)metadata?: Record<string,unknown>// Arbitrary metadata (e.g., sourceId, timestamp, url)}

PackedChunk

Output chunk interface for each selected chunk in the result.

interfacePackedChunk{id: string// Chunk identifiercontent: string// The chunk textscore: number// Original relevance scoretokens: number// Token countposition: number// Zero-based position in the ordered outputmetadata?: Record<string,unknown>// Preserved metadata from the input chunk}

ExcludedChunk

Describes a chunk that was not selected, along with the reason for exclusion.

interfaceExcludedChunk{id: stringcontent: stringscore: numbertokens: numberreason: 'budget'|'redundant'|'strategy'|'max-candidates'redundantWith?: string// ID of the chunk this was redundant withsimilarity?: number// Similarity score that triggered redundancy exclusionmetadata?: Record<string,unknown>}

Exclusion reasons:

ReasonDescription
budgetChunk did not fit within the remaining token budget.
redundantChunk exceeded the similarity threshold compared to a higher-scored chunk.
strategyChunk was excluded by the selection strategy.
max-candidatesChunk was beyond the maxCandidates cutoff.

PackOptions

Full configuration interface for pack() and createPacker().

interfacePackOptions{budget: numberstrategy?: 'greedy'|'mmr'|'knapsack'|'custom'lambda?: numberordering?: 'natural'|'u-shaped'|'chronological'redundancyThreshold?: numbersimilarityMetric?: 'auto'|'cosine'|'jaccard'chunkOverheadTokens?: numbertokenCounter?: (text: string)=>numbermaxCandidates?: numbercustomStrategy?: (chunks: ScoredChunk[],ctx: StrategyContext)=>ScoredChunk[]}
OptionTypeDefaultDescription
budgetnumberrequiredMaximum total tokens for the packed context.
strategystring'greedy'Selection strategy. One of 'greedy', 'mmr', 'knapsack', 'custom'.
lambdanumber0.5MMR trade-off parameter. 1.0 = pure relevance, 0.0 = pure diversity.
orderingstring'natural'Output ordering strategy. One of 'natural', 'u-shaped', 'chronological'.
redundancyThresholdnumberundefinedSimilarity threshold for deduplication. Chunks with similarity >= threshold are removed. Set to 1.0 or omit to disable.
similarityMetricstring'auto'Similarity function. 'auto' uses cosine when embeddings are present, Jaccard otherwise.
chunkOverheadTokensnumber0Extra tokens charged per chunk (separators, citation markers, etc.).
tokenCounterfunctionMath.ceil(text.length / 4)Custom token counting function.
maxCandidatesnumberundefinedLimit the number of input chunks considered. Excess chunks are excluded with reason 'max-candidates'.
customStrategyfunctionundefinedRequired when strategy is 'custom'. Receives candidates and a StrategyContext, returns selected chunks.

StrategyContext

Context object passed to custom strategy functions.

interfaceStrategyContext{budget: number// Token budgetchunkOverheadTokens: number// Per-chunk overheadcountTokens: (text: string)=>number// Active token counter functionoptions: PackOptions// Full options object}

PackReport

Structured report returned with every pack result.

interfacePackReport{tokensUsed: number// Total tokens consumed by selected chunks (including overhead)budget: number// The token budget that was providedtokensRemaining: number// budget - tokensUsedutilization: number// tokensUsed / budget (range 0-1)selectedCount: number// Number of chunks selectedexcludedCount: number// Number of chunks excludedstrategy: string// Strategy that was usedordering: string// Ordering that was appliedexcluded: ExcludedChunk[]// Details on every excluded chunktimestamp: string// ISO 8601 timestamp of the pack operationdurationMs: number// Wall-clock duration in milliseconds}

PackResult

Top-level return type from pack().

interfacePackResult{chunks: PackedChunk[]// Selected and ordered chunksreport: PackReport// Structured packing report}

Configuration

Selection Strategies

Greedy (default)

Sorts chunks by score descending and selects greedily until the budget is full. Time complexity: O(n log n).

constresult=awaitpack(chunks,{budget: 4000})// or explicitly:constresult=awaitpack(chunks,{budget: 4000,strategy: 'greedy'})

MMR (Maximal Marginal Relevance)

Iteratively selects the chunk that maximizes lambda * relevance - (1 - lambda) * maxSimilarityToSelected. Balances relevance and diversity.

constresult=awaitpack(chunks,{budget: 4000,strategy: 'mmr',lambda: 0.7,// 1.0 = pure relevance (greedy-like), 0.0 = pure diversity})

When embeddings are provided on ScoredChunk.embedding, MMR uses cosine similarity for diversity computation. Otherwise it falls back to trigram Jaccard similarity.

Knapsack (0/1 Dynamic Programming)

Solves the 0/1 knapsack problem to maximize total score within the exact token budget. Finds the globally optimal subset, not just the greedy-best.

constresult=awaitpack(chunks,{budget: 4000,strategy: 'knapsack',})

For budgets exceeding 5,000 tokens, the knapsack strategy automatically falls back to greedy to avoid excessive memory and computation costs.

Custom Strategy

Supply your own selection function.

constresult=awaitpack(chunks,{budget: 4000,strategy: 'custom',customStrategy: (candidates,ctx)=>{// Select only high-confidence chunksreturncandidates.filter(c=>c.score>0.8)},})

The customStrategy function receives the full candidate list (after redundancy filtering) and a StrategyContext. It must return the subset of ScoredChunk objects to include.

Ordering Strategies

Natural (default)

Chunks are output in the order determined by the selection strategy (score descending for greedy).

U-Shaped

Places the highest-relevance chunks at the beginning and end of the context, with lower-relevance chunks in the middle. This counters the "lost-in-the-middle" effect where LLMs underweight information in the middle of long contexts.

constresult=awaitpack(chunks,{budget: 4000,ordering: 'u-shaped'})

Chronological

Sorts chunks by metadata.timestamp ascending. Useful for time-sensitive contexts where temporal order matters.

constresult=awaitpack(chunks,{budget: 4000,ordering: 'chronological'})

Requires metadata.timestamp (numeric) on each chunk.

Token Counting

The built-in token counter uses Math.ceil(text.length / 4) as a fast approximation suitable for GPT-family models. For exact counts, provide a custom counter:

import{encode}from'gpt-tokenizer'constresult=awaitpack(chunks,{budget: 4000,tokenCounter: (text)=>encode(text).length,})

You can also pre-compute token counts by setting tokens on each ScoredChunk, which bypasses the counter entirely.

Chunk Overhead

Account for per-chunk formatting overhead (separators, citation markers, XML tags) with chunkOverheadTokens:

constresult=awaitpack(chunks,{budget: 4000,chunkOverheadTokens: 10,// 10 extra tokens per chunk for formatting})

The overhead is added to each chunk's token count during both budget calculations and report metrics.


Error Handling

context-packer throws PackError instances for invalid configurations. Each error includes a machine-readable code and an optional details object.

import{pack,PackError}from'context-packer'try{awaitpack(chunks,{budget: -1})}catch(err){if(errinstanceofPackError){console.error(err.code)// 'INVALID_BUDGET'console.error(err.message)// 'Budget must be positive'console.error(err.details)// undefined (or additional context)}}

Error Codes

CodeThrown When
INVALID_BUDGETbudget is zero or negative.
MISSING_CUSTOM_STRATEGYstrategy is 'custom' and customStrategy is not provided.
INVALID_STRATEGYAn unknown strategy value was provided.
DIMENSION_MISMATCHEmbedding vectors have different dimensions in cosine similarity.
MISSING_EMBEDDINGSsimilarityMetric is 'cosine' but one or both chunks lack embeddings.

When no chunks fit the budget, pack returns an empty chunks array with a valid PackReport rather than throwing. Check report.selectedCount === 0 to detect this case.


Advanced Usage

Redundancy Deduplication

Remove near-duplicate chunks before selection to maximize information density:

constresult=awaitpack(chunks,{budget: 4000,redundancyThreshold: 0.85,similarityMetric: 'auto',})

Chunks sorted by score descending are compared pairwise against already-confirmed chunks. If a chunk's similarity to any confirmed chunk meets or exceeds the threshold, it is excluded with reason 'redundant'. The excluded entry includes redundantWith (the ID of the similar chunk) and similarity (the computed score).

Set redundancyThreshold to 1.0 or omit it to disable deduplication.

Similarity metrics:

  • 'auto' (default) -- Uses cosine similarity when both chunks have embedding vectors, Jaccard trigram similarity otherwise.
  • 'cosine' -- Forces cosine similarity. Falls back to Jaccard if embeddings are missing.
  • 'jaccard' -- Always uses trigram Jaccard similarity over chunk text content.

Embedding-Based Diversity

For best results with MMR or redundancy filtering, provide embedding vectors:

constchunks: ScoredChunk[]=[{content: 'Document about authentication',score: 0.92,tokens: 50,embedding: [0.12,0.45,0.78,/* ... */],},{content: 'Document about authorization',score: 0.85,tokens: 40,embedding: [0.11,0.43,0.80,/* ... */],},]constresult=awaitpack(chunks,{budget: 4000,strategy: 'mmr',lambda: 0.6,redundancyThreshold: 0.9,})

Limiting Candidates

When working with large candidate sets, use maxCandidates to cap the number of chunks considered:

constresult=awaitpack(largeChunkSet,{budget: 4000,maxCandidates: 50,// Only consider the first 50 chunks})

Chunks beyond the limit are excluded with reason 'max-candidates' and appear in report.excluded.

Inspecting the Pack Report

The PackReport provides full transparency into packing decisions:

const{chunks: packed, report }=awaitpack(candidates,{budget: 4000,strategy: 'mmr',lambda: 0.7,redundancyThreshold: 0.85,})console.log(`Strategy: ${report.strategy}`)console.log(`Ordering: ${report.ordering}`)console.log(`Utilization: ${(report.utilization*100).toFixed(1)}%`)console.log(`Selected: ${report.selectedCount}, Excluded: ${report.excludedCount}`)console.log(`Duration: ${report.durationMs}ms`)for(constexofreport.excluded){console.log(` Excluded "${ex.id}": ${ex.reason}`)if(ex.reason==='redundant'){console.log(` Redundant with: ${ex.redundantWith} (similarity: ${ex.similarity})`)}}

Combining Strategies with Ordering

Pair any selection strategy with any ordering strategy:

// MMR selection with U-shaped ordering for maximum qualityconstresult=awaitpack(chunks,{budget: 4000,strategy: 'mmr',lambda: 0.6,ordering: 'u-shaped',redundancyThreshold: 0.85,chunkOverheadTokens: 5,})

TypeScript

context-packer is written in TypeScript and ships with declaration files. All public types are exported from the package entry point:

import{pack,createPacker,PackError}from'context-packer'importtype{ScoredChunk,PackedChunk,ExcludedChunk,PackOptions,StrategyContext,PackReport,PackResult,}from'context-packer'

The package targets ES2022 and compiles to CommonJS. Declaration maps are included for IDE navigation into source types.


License

MIT

About

Optimally pack retrieved chunks into an LLM context window

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

context-packer

Budget-aware, diversity-maximizing chunk packing for LLM context windows.

npm versionnpm downloadslicensenode


Description

context-packer selects and arranges the optimal subset of retrieved chunks to fit within a fixed token budget. Every RAG pipeline must decide which chunks to include, how many tokens they consume, and in what order to place them. This package solves all three problems in a single API call.

The library provides multiple selection strategies (greedy, MMR, knapsack, custom), redundancy deduplication via configurable similarity thresholds, and positional reordering to counter the "lost-in-the-middle" effect documented by Liu et al. (2023). Every call returns a structured PackReport explaining exactly which chunks were selected or excluded and why.

Zero runtime dependencies. Written in TypeScript with full type exports.


Installation

npm install context-packer

Requires Node.js 18 or later.


Quick Start

import{pack}from'context-packer'importtype{ScoredChunk}from'context-packer'constchunks: ScoredChunk[]=[{content: 'Relevant document about authentication',score: 0.92,tokens: 50},{content: 'Relevant document about authorization',score: 0.85,tokens: 40},{content: 'Tangentially related document',score: 0.61,tokens: 80},]const{chunks: packed, report }=awaitpack(chunks,{budget: 100})console.log(report.selectedCount)// 2console.log(report.tokensUsed)// 90console.log(report.tokensRemaining)// 10console.log(report.utilization)// 0.9

Features

  • Multiple selection strategies -- Greedy, Maximal Marginal Relevance (MMR), 0/1 knapsack dynamic programming, and custom strategy support.
  • Redundancy deduplication -- Filters near-duplicate chunks before selection using trigram Jaccard similarity or cosine similarity over embedding vectors.
  • Positional reordering -- U-shaped ordering places high-relevance chunks at the beginning and end of the context, countering the lost-in-the-middle effect.
  • Hard token budget enforcement -- Total token count of selected chunks never exceeds the budget, inclusive of configurable per-chunk overhead.
  • Pluggable token counter -- Ships with a character-based approximation (Math.ceil(text.length / 4)). Swap in tiktoken, gpt-tokenizer, or any other counter.
  • Structured pack reports -- Every call returns a PackReport with utilization metrics, excluded chunk reasons, timing, and strategy metadata.
  • Factory pattern -- createPacker produces a reusable packer instance with fixed configuration for repeated use across queries.
  • Zero runtime dependencies -- No production dependencies to audit or maintain.
  • Full TypeScript support -- Ships with declaration files and source maps.

API Reference

pack(chunks, options)

Selects and orders the best subset of chunks that fit within the token budget.

functionpack(chunks: ScoredChunk[],options: PackOptions): Promise<PackResult>

Parameters:

ParameterTypeDescription
chunksScoredChunk[]Array of retrieved chunks with relevance scores.
optionsPackOptionsPacking configuration including budget, strategy, and ordering.

Returns:Promise<PackResult> containing the selected chunks array and a report.

Example:

constresult=awaitpack(chunks,{budget: 4000,strategy: 'mmr',lambda: 0.7,ordering: 'u-shaped',redundancyThreshold: 0.85,})

createPacker(config)

Creates a reusable packer instance with fixed configuration.

functioncreatePacker(config: PackOptions): {pack: (chunks: ScoredChunk[])=>Promise<PackResult>}

Parameters:

ParameterTypeDescription
configPackOptionsFixed packing configuration applied to every call.

Returns: An object with a pack method that accepts only a ScoredChunk[] array.

Example:

constpacker=createPacker({budget: 4000,strategy: 'mmr',lambda: 0.7})constresult1=awaitpacker.pack(chunksFromQuery1)constresult2=awaitpacker.pack(chunksFromQuery2)

PackError

Custom error class thrown for invalid configurations.

classPackErrorextendsError{readonlycode: stringreadonlydetails?: Record<string,unknown>}

Error codes:

CodeCondition
INVALID_BUDGETbudget is zero or negative.
MISSING_CUSTOM_STRATEGYstrategy is 'custom' but no customStrategy function was provided.
INVALID_STRATEGYAn unknown strategy value was provided.
DIMENSION_MISMATCHEmbedding vectors have different dimensions in cosine similarity.
MISSING_EMBEDDINGSsimilarityMetric is 'cosine' but one or both chunks lack embeddings.

ScoredChunk

Input chunk interface representing a retrieved document segment.

interfaceScoredChunk{content: string// The chunk textscore: number// Relevance score (typically 0-1, higher is better)id?: string// Unique identifier (auto-generated as "chunk-N" if omitted)tokens?: number// Pre-computed token count (skips token counting if provided)embedding?: number[]// Embedding vector (enables cosine similarity for MMR/dedup)metadata?: Record<string,unknown>// Arbitrary metadata (e.g., sourceId, timestamp, url)}

PackedChunk

Output chunk interface for each selected chunk in the result.

interfacePackedChunk{id: string// Chunk identifiercontent: string// The chunk textscore: number// Original relevance scoretokens: number// Token countposition: number// Zero-based position in the ordered outputmetadata?: Record<string,unknown>// Preserved metadata from the input chunk}

ExcludedChunk

Describes a chunk that was not selected, along with the reason for exclusion.

interfaceExcludedChunk{id: stringcontent: stringscore: numbertokens: numberreason: 'budget'|'redundant'|'strategy'|'max-candidates'redundantWith?: string// ID of the chunk this was redundant withsimilarity?: number// Similarity score that triggered redundancy exclusionmetadata?: Record<string,unknown>}

Exclusion reasons:

ReasonDescription
budgetChunk did not fit within the remaining token budget.
redundantChunk exceeded the similarity threshold compared to a higher-scored chunk.
strategyChunk was excluded by the selection strategy.
max-candidatesChunk was beyond the maxCandidates cutoff.

PackOptions

Full configuration interface for pack() and createPacker().

interfacePackOptions{budget: numberstrategy?: 'greedy'|'mmr'|'knapsack'|'custom'lambda?: numberordering?: 'natural'|'u-shaped'|'chronological'redundancyThreshold?: numbersimilarityMetric?: 'auto'|'cosine'|'jaccard'chunkOverheadTokens?: numbertokenCounter?: (text: string)=>numbermaxCandidates?: numbercustomStrategy?: (chunks: ScoredChunk[],ctx: StrategyContext)=>ScoredChunk[]}
OptionTypeDefaultDescription
budgetnumberrequiredMaximum total tokens for the packed context.
strategystring'greedy'Selection strategy. One of 'greedy', 'mmr', 'knapsack', 'custom'.
lambdanumber0.5MMR trade-off parameter. 1.0 = pure relevance, 0.0 = pure diversity.
orderingstring'natural'Output ordering strategy. One of 'natural', 'u-shaped', 'chronological'.
redundancyThresholdnumberundefinedSimilarity threshold for deduplication. Chunks with similarity >= threshold are removed. Set to 1.0 or omit to disable.
similarityMetricstring'auto'Similarity function. 'auto' uses cosine when embeddings are present, Jaccard otherwise.
chunkOverheadTokensnumber0Extra tokens charged per chunk (separators, citation markers, etc.).
tokenCounterfunctionMath.ceil(text.length / 4)Custom token counting function.
maxCandidatesnumberundefinedLimit the number of input chunks considered. Excess chunks are excluded with reason 'max-candidates'.
customStrategyfunctionundefinedRequired when strategy is 'custom'. Receives candidates and a StrategyContext, returns selected chunks.

StrategyContext

Context object passed to custom strategy functions.

interfaceStrategyContext{budget: number// Token budgetchunkOverheadTokens: number// Per-chunk overheadcountTokens: (text: string)=>number// Active token counter functionoptions: PackOptions// Full options object}

PackReport

Structured report returned with every pack result.

interfacePackReport{tokensUsed: number// Total tokens consumed by selected chunks (including overhead)budget: number// The token budget that was providedtokensRemaining: number// budget - tokensUsedutilization: number// tokensUsed / budget (range 0-1)selectedCount: number// Number of chunks selectedexcludedCount: number// Number of chunks excludedstrategy: string// Strategy that was usedordering: string// Ordering that was appliedexcluded: ExcludedChunk[]// Details on every excluded chunktimestamp: string// ISO 8601 timestamp of the pack operationdurationMs: number// Wall-clock duration in milliseconds}

PackResult

Top-level return type from pack().

interfacePackResult{chunks: PackedChunk[]// Selected and ordered chunksreport: PackReport// Structured packing report}

Configuration

Selection Strategies

Greedy (default)

Sorts chunks by score descending and selects greedily until the budget is full. Time complexity: O(n log n).

constresult=awaitpack(chunks,{budget: 4000})// or explicitly:constresult=awaitpack(chunks,{budget: 4000,strategy: 'greedy'})

MMR (Maximal Marginal Relevance)

Iteratively selects the chunk that maximizes lambda * relevance - (1 - lambda) * maxSimilarityToSelected. Balances relevance and diversity.

constresult=awaitpack(chunks,{budget: 4000,strategy: 'mmr',lambda: 0.7,// 1.0 = pure relevance (greedy-like), 0.0 = pure diversity})

When embeddings are provided on ScoredChunk.embedding, MMR uses cosine similarity for diversity computation. Otherwise it falls back to trigram Jaccard similarity.

Knapsack (0/1 Dynamic Programming)

Solves the 0/1 knapsack problem to maximize total score within the exact token budget. Finds the globally optimal subset, not just the greedy-best.

constresult=awaitpack(chunks,{budget: 4000,strategy: 'knapsack',})

For budgets exceeding 5,000 tokens, the knapsack strategy automatically falls back to greedy to avoid excessive memory and computation costs.

Custom Strategy

Supply your own selection function.

constresult=awaitpack(chunks,{budget: 4000,strategy: 'custom',customStrategy: (candidates,ctx)=>{// Select only high-confidence chunksreturncandidates.filter(c=>c.score>0.8)},})

The customStrategy function receives the full candidate list (after redundancy filtering) and a StrategyContext. It must return the subset of ScoredChunk objects to include.

Ordering Strategies

Natural (default)

Chunks are output in the order determined by the selection strategy (score descending for greedy).

U-Shaped

Places the highest-relevance chunks at the beginning and end of the context, with lower-relevance chunks in the middle. This counters the "lost-in-the-middle" effect where LLMs underweight information in the middle of long contexts.

constresult=awaitpack(chunks,{budget: 4000,ordering: 'u-shaped'})

Chronological

Sorts chunks by metadata.timestamp ascending. Useful for time-sensitive contexts where temporal order matters.

constresult=awaitpack(chunks,{budget: 4000,ordering: 'chronological'})

Requires metadata.timestamp (numeric) on each chunk.

Token Counting

The built-in token counter uses Math.ceil(text.length / 4) as a fast approximation suitable for GPT-family models. For exact counts, provide a custom counter:

import{encode}from'gpt-tokenizer'constresult=awaitpack(chunks,{budget: 4000,tokenCounter: (text)=>encode(text).length,})

You can also pre-compute token counts by setting tokens on each ScoredChunk, which bypasses the counter entirely.

Chunk Overhead

Account for per-chunk formatting overhead (separators, citation markers, XML tags) with chunkOverheadTokens:

constresult=awaitpack(chunks,{budget: 4000,chunkOverheadTokens: 10,// 10 extra tokens per chunk for formatting})

The overhead is added to each chunk's token count during both budget calculations and report metrics.


Error Handling

context-packer throws PackError instances for invalid configurations. Each error includes a machine-readable code and an optional details object.

import{pack,PackError}from'context-packer'try{awaitpack(chunks,{budget: -1})}catch(err){if(errinstanceofPackError){console.error(err.code)// 'INVALID_BUDGET'console.error(err.message)// 'Budget must be positive'console.error(err.details)// undefined (or additional context)}}

Error Codes

CodeThrown When
INVALID_BUDGETbudget is zero or negative.
MISSING_CUSTOM_STRATEGYstrategy is 'custom' and customStrategy is not provided.
INVALID_STRATEGYAn unknown strategy value was provided.
DIMENSION_MISMATCHEmbedding vectors have different dimensions in cosine similarity.
MISSING_EMBEDDINGSsimilarityMetric is 'cosine' but one or both chunks lack embeddings.

When no chunks fit the budget, pack returns an empty chunks array with a valid PackReport rather than throwing. Check report.selectedCount === 0 to detect this case.


Advanced Usage

Redundancy Deduplication

Remove near-duplicate chunks before selection to maximize information density:

constresult=awaitpack(chunks,{budget: 4000,redundancyThreshold: 0.85,similarityMetric: 'auto',})

Chunks sorted by score descending are compared pairwise against already-confirmed chunks. If a chunk's similarity to any confirmed chunk meets or exceeds the threshold, it is excluded with reason 'redundant'. The excluded entry includes redundantWith (the ID of the similar chunk) and similarity (the computed score).

Set redundancyThreshold to 1.0 or omit it to disable deduplication.

Similarity metrics:

  • 'auto' (default) -- Uses cosine similarity when both chunks have embedding vectors, Jaccard trigram similarity otherwise.
  • 'cosine' -- Forces cosine similarity. Falls back to Jaccard if embeddings are missing.
  • 'jaccard' -- Always uses trigram Jaccard similarity over chunk text content.

Embedding-Based Diversity

For best results with MMR or redundancy filtering, provide embedding vectors:

constchunks: ScoredChunk[]=[{content: 'Document about authentication',score: 0.92,tokens: 50,embedding: [0.12,0.45,0.78,/* ... */],},{content: 'Document about authorization',score: 0.85,tokens: 40,embedding: [0.11,0.43,0.80,/* ... */],},]constresult=awaitpack(chunks,{budget: 4000,strategy: 'mmr',lambda: 0.6,redundancyThreshold: 0.9,})

Limiting Candidates

When working with large candidate sets, use maxCandidates to cap the number of chunks considered:

constresult=awaitpack(largeChunkSet,{budget: 4000,maxCandidates: 50,// Only consider the first 50 chunks})

Chunks beyond the limit are excluded with reason 'max-candidates' and appear in report.excluded.

Inspecting the Pack Report

The PackReport provides full transparency into packing decisions:

const{chunks: packed, report }=awaitpack(candidates,{budget: 4000,strategy: 'mmr',lambda: 0.7,redundancyThreshold: 0.85,})console.log(`Strategy: ${report.strategy}`)console.log(`Ordering: ${report.ordering}`)console.log(`Utilization: ${(report.utilization*100).toFixed(1)}%`)console.log(`Selected: ${report.selectedCount}, Excluded: ${report.excludedCount}`)console.log(`Duration: ${report.durationMs}ms`)for(constexofreport.excluded){console.log(` Excluded "${ex.id}": ${ex.reason}`)if(ex.reason==='redundant'){console.log(` Redundant with: ${ex.redundantWith} (similarity: ${ex.similarity})`)}}

Combining Strategies with Ordering

Pair any selection strategy with any ordering strategy:

// MMR selection with U-shaped ordering for maximum qualityconstresult=awaitpack(chunks,{budget: 4000,strategy: 'mmr',lambda: 0.6,ordering: 'u-shaped',redundancyThreshold: 0.85,chunkOverheadTokens: 5,})

TypeScript

context-packer is written in TypeScript and ships with declaration files. All public types are exported from the package entry point:

import{pack,createPacker,PackError}from'context-packer'importtype{ScoredChunk,PackedChunk,ExcludedChunk,PackOptions,StrategyContext,PackReport,PackResult,}from'context-packer'

The package targets ES2022 and compiles to CommonJS. Declaration maps are included for IDE navigation into source types.


License

MIT

About

Optimally pack retrieved chunks into an LLM context window

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

context-packer

Budget-aware, diversity-maximizing chunk packing for LLM context windows.

npm versionnpm downloadslicensenode


Description

context-packer selects and arranges the optimal subset of retrieved chunks to fit within a fixed token budget. Every RAG pipeline must decide which chunks to include, how many tokens they consume, and in what order to place them. This package solves all three problems in a single API call.

The library provides multiple selection strategies (greedy, MMR, knapsack, custom), redundancy deduplication via configurable similarity thresholds, and positional reordering to counter the "lost-in-the-middle" effect documented by Liu et al. (2023). Every call returns a structured PackReport explaining exactly which chunks were selected or excluded and why.

Zero runtime dependencies. Written in TypeScript with full type exports.


Installation

npm install context-packer

Requires Node.js 18 or later.


Quick Start

import{pack}from'context-packer'importtype{ScoredChunk}from'context-packer'constchunks: ScoredChunk[]=[{content: 'Relevant document about authentication',score: 0.92,tokens: 50},{content: 'Relevant document about authorization',score: 0.85,tokens: 40},{content: 'Tangentially related document',score: 0.61,tokens: 80},]const{chunks: packed, report }=awaitpack(chunks,{budget: 100})console.log(report.selectedCount)// 2console.log(report.tokensUsed)// 90console.log(report.tokensRemaining)// 10console.log(report.utilization)// 0.9

Features

  • Multiple selection strategies -- Greedy, Maximal Marginal Relevance (MMR), 0/1 knapsack dynamic programming, and custom strategy support.
  • Redundancy deduplication -- Filters near-duplicate chunks before selection using trigram Jaccard similarity or cosine similarity over embedding vectors.
  • Positional reordering -- U-shaped ordering places high-relevance chunks at the beginning and end of the context, countering the lost-in-the-middle effect.
  • Hard token budget enforcement -- Total token count of selected chunks never exceeds the budget, inclusive of configurable per-chunk overhead.
  • Pluggable token counter -- Ships with a character-based approximation (Math.ceil(text.length / 4)). Swap in tiktoken, gpt-tokenizer, or any other counter.
  • Structured pack reports -- Every call returns a PackReport with utilization metrics, excluded chunk reasons, timing, and strategy metadata.
  • Factory pattern -- createPacker produces a reusable packer instance with fixed configuration for repeated use across queries.
  • Zero runtime dependencies -- No production dependencies to audit or maintain.
  • Full TypeScript support -- Ships with declaration files and source maps.

API Reference

pack(chunks, options)

Selects and orders the best subset of chunks that fit within the token budget.

functionpack(chunks: ScoredChunk[],options: PackOptions): Promise<PackResult>

Parameters:

ParameterTypeDescription
chunksScoredChunk[]Array of retrieved chunks with relevance scores.
optionsPackOptionsPacking configuration including budget, strategy, and ordering.

Returns:Promise<PackResult> containing the selected chunks array and a report.

Example:

constresult=awaitpack(chunks,{budget: 4000,strategy: 'mmr',lambda: 0.7,ordering: 'u-shaped',redundancyThreshold: 0.85,})

createPacker(config)

Creates a reusable packer instance with fixed configuration.

functioncreatePacker(config: PackOptions): {pack: (chunks: ScoredChunk[])=>Promise<PackResult>}

Parameters:

ParameterTypeDescription
configPackOptionsFixed packing configuration applied to every call.

Returns: An object with a pack method that accepts only a ScoredChunk[] array.

Example:

constpacker=createPacker({budget: 4000,strategy: 'mmr',lambda: 0.7})constresult1=awaitpacker.pack(chunksFromQuery1)constresult2=awaitpacker.pack(chunksFromQuery2)

PackError

Custom error class thrown for invalid configurations.

classPackErrorextendsError{readonlycode: stringreadonlydetails?: Record<string,unknown>}

Error codes:

CodeCondition
INVALID_BUDGETbudget is zero or negative.
MISSING_CUSTOM_STRATEGYstrategy is 'custom' but no customStrategy function was provided.
INVALID_STRATEGYAn unknown strategy value was provided.
DIMENSION_MISMATCHEmbedding vectors have different dimensions in cosine similarity.
MISSING_EMBEDDINGSsimilarityMetric is 'cosine' but one or both chunks lack embeddings.

ScoredChunk

Input chunk interface representing a retrieved document segment.

interfaceScoredChunk{content: string// The chunk textscore: number// Relevance score (typically 0-1, higher is better)id?: string// Unique identifier (auto-generated as "chunk-N" if omitted)tokens?: number// Pre-computed token count (skips token counting if provided)embedding?: number[]// Embedding vector (enables cosine similarity for MMR/dedup)metadata?: Record<string,unknown>// Arbitrary metadata (e.g., sourceId, timestamp, url)}

PackedChunk

Output chunk interface for each selected chunk in the result.

interfacePackedChunk{id: string// Chunk identifiercontent: string// The chunk textscore: number// Original relevance scoretokens: number// Token countposition: number// Zero-based position in the ordered outputmetadata?: Record<string,unknown>// Preserved metadata from the input chunk}

ExcludedChunk

Describes a chunk that was not selected, along with the reason for exclusion.

interfaceExcludedChunk{id: stringcontent: stringscore: numbertokens: numberreason: 'budget'|'redundant'|'strategy'|'max-candidates'redundantWith?: string// ID of the chunk this was redundant withsimilarity?: number// Similarity score that triggered redundancy exclusionmetadata?: Record<string,unknown>}

Exclusion reasons:

ReasonDescription
budgetChunk did not fit within the remaining token budget.
redundantChunk exceeded the similarity threshold compared to a higher-scored chunk.
strategyChunk was excluded by the selection strategy.
max-candidatesChunk was beyond the maxCandidates cutoff.

PackOptions

Full configuration interface for pack() and createPacker().

interfacePackOptions{budget: numberstrategy?: 'greedy'|'mmr'|'knapsack'|'custom'lambda?: numberordering?: 'natural'|'u-shaped'|'chronological'redundancyThreshold?: numbersimilarityMetric?: 'auto'|'cosine'|'jaccard'chunkOverheadTokens?: numbertokenCounter?: (text: string)=>numbermaxCandidates?: numbercustomStrategy?: (chunks: ScoredChunk[],ctx: StrategyContext)=>ScoredChunk[]}
OptionTypeDefaultDescription
budgetnumberrequiredMaximum total tokens for the packed context.
strategystring'greedy'Selection strategy. One of 'greedy', 'mmr', 'knapsack', 'custom'.
lambdanumber0.5MMR trade-off parameter. 1.0 = pure relevance, 0.0 = pure diversity.
orderingstring'natural'Output ordering strategy. One of 'natural', 'u-shaped', 'chronological'.
redundancyThresholdnumberundefinedSimilarity threshold for deduplication. Chunks with similarity >= threshold are removed. Set to 1.0 or omit to disable.
similarityMetricstring'auto'Similarity function. 'auto' uses cosine when embeddings are present, Jaccard otherwise.
chunkOverheadTokensnumber0Extra tokens charged per chunk (separators, citation markers, etc.).
tokenCounterfunctionMath.ceil(text.length / 4)Custom token counting function.
maxCandidatesnumberundefinedLimit the number of input chunks considered. Excess chunks are excluded with reason 'max-candidates'.
customStrategyfunctionundefinedRequired when strategy is 'custom'. Receives candidates and a StrategyContext, returns selected chunks.

StrategyContext

Context object passed to custom strategy functions.

interfaceStrategyContext{budget: number// Token budgetchunkOverheadTokens: number// Per-chunk overheadcountTokens: (text: string)=>number// Active token counter functionoptions: PackOptions// Full options object}

PackReport

Structured report returned with every pack result.

interfacePackReport{tokensUsed: number// Total tokens consumed by selected chunks (including overhead)budget: number// The token budget that was providedtokensRemaining: number// budget - tokensUsedutilization: number// tokensUsed / budget (range 0-1)selectedCount: number// Number of chunks selectedexcludedCount: number// Number of chunks excludedstrategy: string// Strategy that was usedordering: string// Ordering that was appliedexcluded: ExcludedChunk[]// Details on every excluded chunktimestamp: string// ISO 8601 timestamp of the pack operationdurationMs: number// Wall-clock duration in milliseconds}

PackResult

Top-level return type from pack().

interfacePackResult{chunks: PackedChunk[]// Selected and ordered chunksreport: PackReport// Structured packing report}

Configuration

Selection Strategies

Greedy (default)

Sorts chunks by score descending and selects greedily until the budget is full. Time complexity: O(n log n).

constresult=awaitpack(chunks,{budget: 4000})// or explicitly:constresult=awaitpack(chunks,{budget: 4000,strategy: 'greedy'})

MMR (Maximal Marginal Relevance)

Iteratively selects the chunk that maximizes lambda * relevance - (1 - lambda) * maxSimilarityToSelected. Balances relevance and diversity.

constresult=awaitpack(chunks,{budget: 4000,strategy: 'mmr',lambda: 0.7,// 1.0 = pure relevance (greedy-like), 0.0 = pure diversity})

When embeddings are provided on ScoredChunk.embedding, MMR uses cosine similarity for diversity computation. Otherwise it falls back to trigram Jaccard similarity.

Knapsack (0/1 Dynamic Programming)

Solves the 0/1 knapsack problem to maximize total score within the exact token budget. Finds the globally optimal subset, not just the greedy-best.

constresult=awaitpack(chunks,{budget: 4000,strategy: 'knapsack',})

For budgets exceeding 5,000 tokens, the knapsack strategy automatically falls back to greedy to avoid excessive memory and computation costs.

Custom Strategy

Supply your own selection function.

constresult=awaitpack(chunks,{budget: 4000,strategy: 'custom',customStrategy: (candidates,ctx)=>{// Select only high-confidence chunksreturncandidates.filter(c=>c.score>0.8)},})

The customStrategy function receives the full candidate list (after redundancy filtering) and a StrategyContext. It must return the subset of ScoredChunk objects to include.

Ordering Strategies

Natural (default)

Chunks are output in the order determined by the selection strategy (score descending for greedy).

U-Shaped

Places the highest-relevance chunks at the beginning and end of the context, with lower-relevance chunks in the middle. This counters the "lost-in-the-middle" effect where LLMs underweight information in the middle of long contexts.

constresult=awaitpack(chunks,{budget: 4000,ordering: 'u-shaped'})

Chronological

Sorts chunks by metadata.timestamp ascending. Useful for time-sensitive contexts where temporal order matters.

constresult=awaitpack(chunks,{budget: 4000,ordering: 'chronological'})

Requires metadata.timestamp (numeric) on each chunk.

Token Counting

The built-in token counter uses Math.ceil(text.length / 4) as a fast approximation suitable for GPT-family models. For exact counts, provide a custom counter:

import{encode}from'gpt-tokenizer'constresult=awaitpack(chunks,{budget: 4000,tokenCounter: (text)=>encode(text).length,})

You can also pre-compute token counts by setting tokens on each ScoredChunk, which bypasses the counter entirely.

Chunk Overhead

Account for per-chunk formatting overhead (separators, citation markers, XML tags) with chunkOverheadTokens:

constresult=awaitpack(chunks,{budget: 4000,chunkOverheadTokens: 10,// 10 extra tokens per chunk for formatting})

The overhead is added to each chunk's token count during both budget calculations and report metrics.


Error Handling

context-packer throws PackError instances for invalid configurations. Each error includes a machine-readable code and an optional details object.

import{pack,PackError}from'context-packer'try{awaitpack(chunks,{budget: -1})}catch(err){if(errinstanceofPackError){console.error(err.code)// 'INVALID_BUDGET'console.error(err.message)// 'Budget must be positive'console.error(err.details)// undefined (or additional context)}}

Error Codes

CodeThrown When
INVALID_BUDGETbudget is zero or negative.
MISSING_CUSTOM_STRATEGYstrategy is 'custom' and customStrategy is not provided.
INVALID_STRATEGYAn unknown strategy value was provided.
DIMENSION_MISMATCHEmbedding vectors have different dimensions in cosine similarity.
MISSING_EMBEDDINGSsimilarityMetric is 'cosine' but one or both chunks lack embeddings.

When no chunks fit the budget, pack returns an empty chunks array with a valid PackReport rather than throwing. Check report.selectedCount === 0 to detect this case.


Advanced Usage

Redundancy Deduplication

Remove near-duplicate chunks before selection to maximize information density:

constresult=awaitpack(chunks,{budget: 4000,redundancyThreshold: 0.85,similarityMetric: 'auto',})

Chunks sorted by score descending are compared pairwise against already-confirmed chunks. If a chunk's similarity to any confirmed chunk meets or exceeds the threshold, it is excluded with reason 'redundant'. The excluded entry includes redundantWith (the ID of the similar chunk) and similarity (the computed score).

Set redundancyThreshold to 1.0 or omit it to disable deduplication.

Similarity metrics:

  • 'auto' (default) -- Uses cosine similarity when both chunks have embedding vectors, Jaccard trigram similarity otherwise.
  • 'cosine' -- Forces cosine similarity. Falls back to Jaccard if embeddings are missing.
  • 'jaccard' -- Always uses trigram Jaccard similarity over chunk text content.

Embedding-Based Diversity

For best results with MMR or redundancy filtering, provide embedding vectors:

constchunks: ScoredChunk[]=[{content: 'Document about authentication',score: 0.92,tokens: 50,embedding: [0.12,0.45,0.78,/* ... */],},{content: 'Document about authorization',score: 0.85,tokens: 40,embedding: [0.11,0.43,0.80,/* ... */],},]constresult=awaitpack(chunks,{budget: 4000,strategy: 'mmr',lambda: 0.6,redundancyThreshold: 0.9,})

Limiting Candidates

When working with large candidate sets, use maxCandidates to cap the number of chunks considered:

constresult=awaitpack(largeChunkSet,{budget: 4000,maxCandidates: 50,// Only consider the first 50 chunks})

Chunks beyond the limit are excluded with reason 'max-candidates' and appear in report.excluded.

Inspecting the Pack Report

The PackReport provides full transparency into packing decisions:

const{chunks: packed, report }=awaitpack(candidates,{budget: 4000,strategy: 'mmr',lambda: 0.7,redundancyThreshold: 0.85,})console.log(`Strategy: ${report.strategy}`)console.log(`Ordering: ${report.ordering}`)console.log(`Utilization: ${(report.utilization*100).toFixed(1)}%`)console.log(`Selected: ${report.selectedCount}, Excluded: ${report.excludedCount}`)console.log(`Duration: ${report.durationMs}ms`)for(constexofreport.excluded){console.log(` Excluded "${ex.id}": ${ex.reason}`)if(ex.reason==='redundant'){console.log(` Redundant with: ${ex.redundantWith} (similarity: ${ex.similarity})`)}}

Combining Strategies with Ordering

Pair any selection strategy with any ordering strategy:

// MMR selection with U-shaped ordering for maximum qualityconstresult=awaitpack(chunks,{budget: 4000,strategy: 'mmr',lambda: 0.6,ordering: 'u-shaped',redundancyThreshold: 0.85,chunkOverheadTokens: 5,})

TypeScript

context-packer is written in TypeScript and ships with declaration files. All public types are exported from the package entry point:

import{pack,createPacker,PackError}from'context-packer'importtype{ScoredChunk,PackedChunk,ExcludedChunk,PackOptions,StrategyContext,PackReport,PackResult,}from'context-packer'

The package targets ES2022 and compiles to CommonJS. Declaration maps are included for IDE navigation into source types.


License

MIT

About

Optimally pack retrieved chunks into an LLM context window

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

context-packer

Budget-aware, diversity-maximizing chunk packing for LLM context windows.

npm versionnpm downloadslicensenode


Description

context-packer selects and arranges the optimal subset of retrieved chunks to fit within a fixed token budget. Every RAG pipeline must decide which chunks to include, how many tokens they consume, and in what order to place them. This package solves all three problems in a single API call.

The library provides multiple selection strategies (greedy, MMR, knapsack, custom), redundancy deduplication via configurable similarity thresholds, and positional reordering to counter the "lost-in-the-middle" effect documented by Liu et al. (2023). Every call returns a structured PackReport explaining exactly which chunks were selected or excluded and why.

Zero runtime dependencies. Written in TypeScript with full type exports.


Installation

npm install context-packer

Requires Node.js 18 or later.


Quick Start

import{pack}from'context-packer'importtype{ScoredChunk}from'context-packer'constchunks: ScoredChunk[]=[{content: 'Relevant document about authentication',score: 0.92,tokens: 50},{content: 'Relevant document about authorization',score: 0.85,tokens: 40},{content: 'Tangentially related document',score: 0.61,tokens: 80},]const{chunks: packed, report }=awaitpack(chunks,{budget: 100})console.log(report.selectedCount)// 2console.log(report.tokensUsed)// 90console.log(report.tokensRemaining)// 10console.log(report.utilization)// 0.9

Features

  • Multiple selection strategies -- Greedy, Maximal Marginal Relevance (MMR), 0/1 knapsack dynamic programming, and custom strategy support.
  • Redundancy deduplication -- Filters near-duplicate chunks before selection using trigram Jaccard similarity or cosine similarity over embedding vectors.
  • Positional reordering -- U-shaped ordering places high-relevance chunks at the beginning and end of the context, countering the lost-in-the-middle effect.
  • Hard token budget enforcement -- Total token count of selected chunks never exceeds the budget, inclusive of configurable per-chunk overhead.
  • Pluggable token counter -- Ships with a character-based approximation (Math.ceil(text.length / 4)). Swap in tiktoken, gpt-tokenizer, or any other counter.
  • Structured pack reports -- Every call returns a PackReport with utilization metrics, excluded chunk reasons, timing, and strategy metadata.
  • Factory pattern -- createPacker produces a reusable packer instance with fixed configuration for repeated use across queries.
  • Zero runtime dependencies -- No production dependencies to audit or maintain.
  • Full TypeScript support -- Ships with declaration files and source maps.

API Reference

pack(chunks, options)

Selects and orders the best subset of chunks that fit within the token budget.

functionpack(chunks: ScoredChunk[],options: PackOptions): Promise<PackResult>

Parameters:

ParameterTypeDescription
chunksScoredChunk[]Array of retrieved chunks with relevance scores.
optionsPackOptionsPacking configuration including budget, strategy, and ordering.

Returns:Promise<PackResult> containing the selected chunks array and a report.

Example:

constresult=awaitpack(chunks,{budget: 4000,strategy: 'mmr',lambda: 0.7,ordering: 'u-shaped',redundancyThreshold: 0.85,})

createPacker(config)

Creates a reusable packer instance with fixed configuration.

functioncreatePacker(config: PackOptions): {pack: (chunks: ScoredChunk[])=>Promise<PackResult>}

Parameters:

ParameterTypeDescription
configPackOptionsFixed packing configuration applied to every call.

Returns: An object with a pack method that accepts only a ScoredChunk[] array.

Example:

constpacker=createPacker({budget: 4000,strategy: 'mmr',lambda: 0.7})constresult1=awaitpacker.pack(chunksFromQuery1)constresult2=awaitpacker.pack(chunksFromQuery2)

PackError

Custom error class thrown for invalid configurations.

classPackErrorextendsError{readonlycode: stringreadonlydetails?: Record<string,unknown>}

Error codes:

CodeCondition
INVALID_BUDGETbudget is zero or negative.
MISSING_CUSTOM_STRATEGYstrategy is 'custom' but no customStrategy function was provided.
INVALID_STRATEGYAn unknown strategy value was provided.
DIMENSION_MISMATCHEmbedding vectors have different dimensions in cosine similarity.
MISSING_EMBEDDINGSsimilarityMetric is 'cosine' but one or both chunks lack embeddings.

ScoredChunk

Input chunk interface representing a retrieved document segment.

interfaceScoredChunk{content: string// The chunk textscore: number// Relevance score (typically 0-1, higher is better)id?: string// Unique identifier (auto-generated as "chunk-N" if omitted)tokens?: number// Pre-computed token count (skips token counting if provided)embedding?: number[]// Embedding vector (enables cosine similarity for MMR/dedup)metadata?: Record<string,unknown>// Arbitrary metadata (e.g., sourceId, timestamp, url)}

PackedChunk

Output chunk interface for each selected chunk in the result.

interfacePackedChunk{id: string// Chunk identifiercontent: string// The chunk textscore: number// Original relevance scoretokens: number// Token countposition: number// Zero-based position in the ordered outputmetadata?: Record<string,unknown>// Preserved metadata from the input chunk}

ExcludedChunk

Describes a chunk that was not selected, along with the reason for exclusion.

interfaceExcludedChunk{id: stringcontent: stringscore: numbertokens: numberreason: 'budget'|'redundant'|'strategy'|'max-candidates'redundantWith?: string// ID of the chunk this was redundant withsimilarity?: number// Similarity score that triggered redundancy exclusionmetadata?: Record<string,unknown>}

Exclusion reasons:

ReasonDescription
budgetChunk did not fit within the remaining token budget.
redundantChunk exceeded the similarity threshold compared to a higher-scored chunk.
strategyChunk was excluded by the selection strategy.
max-candidatesChunk was beyond the maxCandidates cutoff.

PackOptions

Full configuration interface for pack() and createPacker().

interfacePackOptions{budget: numberstrategy?: 'greedy'|'mmr'|'knapsack'|'custom'lambda?: numberordering?: 'natural'|'u-shaped'|'chronological'redundancyThreshold?: numbersimilarityMetric?: 'auto'|'cosine'|'jaccard'chunkOverheadTokens?: numbertokenCounter?: (text: string)=>numbermaxCandidates?: numbercustomStrategy?: (chunks: ScoredChunk[],ctx: StrategyContext)=>ScoredChunk[]}
OptionTypeDefaultDescription
budgetnumberrequiredMaximum total tokens for the packed context.
strategystring'greedy'Selection strategy. One of 'greedy', 'mmr', 'knapsack', 'custom'.
lambdanumber0.5MMR trade-off parameter. 1.0 = pure relevance, 0.0 = pure diversity.
orderingstring'natural'Output ordering strategy. One of 'natural', 'u-shaped', 'chronological'.
redundancyThresholdnumberundefinedSimilarity threshold for deduplication. Chunks with similarity >= threshold are removed. Set to 1.0 or omit to disable.
similarityMetricstring'auto'Similarity function. 'auto' uses cosine when embeddings are present, Jaccard otherwise.
chunkOverheadTokensnumber0Extra tokens charged per chunk (separators, citation markers, etc.).
tokenCounterfunctionMath.ceil(text.length / 4)Custom token counting function.
maxCandidatesnumberundefinedLimit the number of input chunks considered. Excess chunks are excluded with reason 'max-candidates'.
customStrategyfunctionundefinedRequired when strategy is 'custom'. Receives candidates and a StrategyContext, returns selected chunks.

StrategyContext

Context object passed to custom strategy functions.

interfaceStrategyContext{budget: number// Token budgetchunkOverheadTokens: number// Per-chunk overheadcountTokens: (text: string)=>number// Active token counter functionoptions: PackOptions// Full options object}

PackReport

Structured report returned with every pack result.

interfacePackReport{tokensUsed: number// Total tokens consumed by selected chunks (including overhead)budget: number// The token budget that was providedtokensRemaining: number// budget - tokensUsedutilization: number// tokensUsed / budget (range 0-1)selectedCount: number// Number of chunks selectedexcludedCount: number// Number of chunks excludedstrategy: string// Strategy that was usedordering: string// Ordering that was appliedexcluded: ExcludedChunk[]// Details on every excluded chunktimestamp: string// ISO 8601 timestamp of the pack operationdurationMs: number// Wall-clock duration in milliseconds}

PackResult

Top-level return type from pack().

interfacePackResult{chunks: PackedChunk[]// Selected and ordered chunksreport: PackReport// Structured packing report}

Configuration

Selection Strategies

Greedy (default)

Sorts chunks by score descending and selects greedily until the budget is full. Time complexity: O(n log n).

constresult=awaitpack(chunks,{budget: 4000})// or explicitly:constresult=awaitpack(chunks,{budget: 4000,strategy: 'greedy'})

MMR (Maximal Marginal Relevance)

Iteratively selects the chunk that maximizes lambda * relevance - (1 - lambda) * maxSimilarityToSelected. Balances relevance and diversity.

constresult=awaitpack(chunks,{budget: 4000,strategy: 'mmr',lambda: 0.7,// 1.0 = pure relevance (greedy-like), 0.0 = pure diversity})

When embeddings are provided on ScoredChunk.embedding, MMR uses cosine similarity for diversity computation. Otherwise it falls back to trigram Jaccard similarity.

Knapsack (0/1 Dynamic Programming)

Solves the 0/1 knapsack problem to maximize total score within the exact token budget. Finds the globally optimal subset, not just the greedy-best.

constresult=awaitpack(chunks,{budget: 4000,strategy: 'knapsack',})

For budgets exceeding 5,000 tokens, the knapsack strategy automatically falls back to greedy to avoid excessive memory and computation costs.

Custom Strategy

Supply your own selection function.

constresult=awaitpack(chunks,{budget: 4000,strategy: 'custom',customStrategy: (candidates,ctx)=>{// Select only high-confidence chunksreturncandidates.filter(c=>c.score>0.8)},})

The customStrategy function receives the full candidate list (after redundancy filtering) and a StrategyContext. It must return the subset of ScoredChunk objects to include.

Ordering Strategies

Natural (default)

Chunks are output in the order determined by the selection strategy (score descending for greedy).

U-Shaped

Places the highest-relevance chunks at the beginning and end of the context, with lower-relevance chunks in the middle. This counters the "lost-in-the-middle" effect where LLMs underweight information in the middle of long contexts.

constresult=awaitpack(chunks,{budget: 4000,ordering: 'u-shaped'})

Chronological

Sorts chunks by metadata.timestamp ascending. Useful for time-sensitive contexts where temporal order matters.

constresult=awaitpack(chunks,{budget: 4000,ordering: 'chronological'})

Requires metadata.timestamp (numeric) on each chunk.

Token Counting

The built-in token counter uses Math.ceil(text.length / 4) as a fast approximation suitable for GPT-family models. For exact counts, provide a custom counter:

import{encode}from'gpt-tokenizer'constresult=awaitpack(chunks,{budget: 4000,tokenCounter: (text)=>encode(text).length,})

You can also pre-compute token counts by setting tokens on each ScoredChunk, which bypasses the counter entirely.

Chunk Overhead

Account for per-chunk formatting overhead (separators, citation markers, XML tags) with chunkOverheadTokens:

constresult=awaitpack(chunks,{budget: 4000,chunkOverheadTokens: 10,// 10 extra tokens per chunk for formatting})

The overhead is added to each chunk's token count during both budget calculations and report metrics.


Error Handling

context-packer throws PackError instances for invalid configurations. Each error includes a machine-readable code and an optional details object.

import{pack,PackError}from'context-packer'try{awaitpack(chunks,{budget: -1})}catch(err){if(errinstanceofPackError){console.error(err.code)// 'INVALID_BUDGET'console.error(err.message)// 'Budget must be positive'console.error(err.details)// undefined (or additional context)}}

Error Codes

CodeThrown When
INVALID_BUDGETbudget is zero or negative.
MISSING_CUSTOM_STRATEGYstrategy is 'custom' and customStrategy is not provided.
INVALID_STRATEGYAn unknown strategy value was provided.
DIMENSION_MISMATCHEmbedding vectors have different dimensions in cosine similarity.
MISSING_EMBEDDINGSsimilarityMetric is 'cosine' but one or both chunks lack embeddings.

When no chunks fit the budget, pack returns an empty chunks array with a valid PackReport rather than throwing. Check report.selectedCount === 0 to detect this case.


Advanced Usage

Redundancy Deduplication

Remove near-duplicate chunks before selection to maximize information density:

constresult=awaitpack(chunks,{budget: 4000,redundancyThreshold: 0.85,similarityMetric: 'auto',})

Chunks sorted by score descending are compared pairwise against already-confirmed chunks. If a chunk's similarity to any confirmed chunk meets or exceeds the threshold, it is excluded with reason 'redundant'. The excluded entry includes redundantWith (the ID of the similar chunk) and similarity (the computed score).

Set redundancyThreshold to 1.0 or omit it to disable deduplication.

Similarity metrics:

  • 'auto' (default) -- Uses cosine similarity when both chunks have embedding vectors, Jaccard trigram similarity otherwise.
  • 'cosine' -- Forces cosine similarity. Falls back to Jaccard if embeddings are missing.
  • 'jaccard' -- Always uses trigram Jaccard similarity over chunk text content.

Embedding-Based Diversity

For best results with MMR or redundancy filtering, provide embedding vectors:

constchunks: ScoredChunk[]=[{content: 'Document about authentication',score: 0.92,tokens: 50,embedding: [0.12,0.45,0.78,/* ... */],},{content: 'Document about authorization',score: 0.85,tokens: 40,embedding: [0.11,0.43,0.80,/* ... */],},]constresult=awaitpack(chunks,{budget: 4000,strategy: 'mmr',lambda: 0.6,redundancyThreshold: 0.9,})

Limiting Candidates

When working with large candidate sets, use maxCandidates to cap the number of chunks considered:

constresult=awaitpack(largeChunkSet,{budget: 4000,maxCandidates: 50,// Only consider the first 50 chunks})

Chunks beyond the limit are excluded with reason 'max-candidates' and appear in report.excluded.

Inspecting the Pack Report

The PackReport provides full transparency into packing decisions:

const{chunks: packed, report }=awaitpack(candidates,{budget: 4000,strategy: 'mmr',lambda: 0.7,redundancyThreshold: 0.85,})console.log(`Strategy: ${report.strategy}`)console.log(`Ordering: ${report.ordering}`)console.log(`Utilization: ${(report.utilization*100).toFixed(1)}%`)console.log(`Selected: ${report.selectedCount}, Excluded: ${report.excludedCount}`)console.log(`Duration: ${report.durationMs}ms`)for(constexofreport.excluded){console.log(` Excluded "${ex.id}": ${ex.reason}`)if(ex.reason==='redundant'){console.log(` Redundant with: ${ex.redundantWith} (similarity: ${ex.similarity})`)}}

Combining Strategies with Ordering

Pair any selection strategy with any ordering strategy:

// MMR selection with U-shaped ordering for maximum qualityconstresult=awaitpack(chunks,{budget: 4000,strategy: 'mmr',lambda: 0.6,ordering: 'u-shaped',redundancyThreshold: 0.85,chunkOverheadTokens: 5,})

TypeScript

context-packer is written in TypeScript and ships with declaration files. All public types are exported from the package entry point:

import{pack,createPacker,PackError}from'context-packer'importtype{ScoredChunk,PackedChunk,ExcludedChunk,PackOptions,StrategyContext,PackReport,PackResult,}from'context-packer'

The package targets ES2022 and compiles to CommonJS. Declaration maps are included for IDE navigation into source types.


License

MIT

About

Optimally pack retrieved chunks into an LLM context window

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

context-packer

Budget-aware, diversity-maximizing chunk packing for LLM context windows.

npm versionnpm downloadslicensenode


Description

context-packer selects and arranges the optimal subset of retrieved chunks to fit within a fixed token budget. Every RAG pipeline must decide which chunks to include, how many tokens they consume, and in what order to place them. This package solves all three problems in a single API call.

The library provides multiple selection strategies (greedy, MMR, knapsack, custom), redundancy deduplication via configurable similarity thresholds, and positional reordering to counter the "lost-in-the-middle" effect documented by Liu et al. (2023). Every call returns a structured PackReport explaining exactly which chunks were selected or excluded and why.

Zero runtime dependencies. Written in TypeScript with full type exports.


Installation

npm install context-packer

Requires Node.js 18 or later.


Quick Start

import{pack}from'context-packer'importtype{ScoredChunk}from'context-packer'constchunks: ScoredChunk[]=[{content: 'Relevant document about authentication',score: 0.92,tokens: 50},{content: 'Relevant document about authorization',score: 0.85,tokens: 40},{content: 'Tangentially related document',score: 0.61,tokens: 80},]const{chunks: packed, report }=awaitpack(chunks,{budget: 100})console.log(report.selectedCount)// 2console.log(report.tokensUsed)// 90console.log(report.tokensRemaining)// 10console.log(report.utilization)// 0.9

Features

  • Multiple selection strategies -- Greedy, Maximal Marginal Relevance (MMR), 0/1 knapsack dynamic programming, and custom strategy support.
  • Redundancy deduplication -- Filters near-duplicate chunks before selection using trigram Jaccard similarity or cosine similarity over embedding vectors.
  • Positional reordering -- U-shaped ordering places high-relevance chunks at the beginning and end of the context, countering the lost-in-the-middle effect.
  • Hard token budget enforcement -- Total token count of selected chunks never exceeds the budget, inclusive of configurable per-chunk overhead.
  • Pluggable token counter -- Ships with a character-based approximation (Math.ceil(text.length / 4)). Swap in tiktoken, gpt-tokenizer, or any other counter.
  • Structured pack reports -- Every call returns a PackReport with utilization metrics, excluded chunk reasons, timing, and strategy metadata.
  • Factory pattern -- createPacker produces a reusable packer instance with fixed configuration for repeated use across queries.
  • Zero runtime dependencies -- No production dependencies to audit or maintain.
  • Full TypeScript support -- Ships with declaration files and source maps.

API Reference

pack(chunks, options)

Selects and orders the best subset of chunks that fit within the token budget.

functionpack(chunks: ScoredChunk[],options: PackOptions): Promise<PackResult>

Parameters:

ParameterTypeDescription
chunksScoredChunk[]Array of retrieved chunks with relevance scores.
optionsPackOptionsPacking configuration including budget, strategy, and ordering.

Returns:Promise<PackResult> containing the selected chunks array and a report.

Example:

constresult=awaitpack(chunks,{budget: 4000,strategy: 'mmr',lambda: 0.7,ordering: 'u-shaped',redundancyThreshold: 0.85,})

createPacker(config)

Creates a reusable packer instance with fixed configuration.

functioncreatePacker(config: PackOptions): {pack: (chunks: ScoredChunk[])=>Promise<PackResult>}

Parameters:

ParameterTypeDescription
configPackOptionsFixed packing configuration applied to every call.

Returns: An object with a pack method that accepts only a ScoredChunk[] array.

Example:

constpacker=createPacker({budget: 4000,strategy: 'mmr',lambda: 0.7})constresult1=awaitpacker.pack(chunksFromQuery1)constresult2=awaitpacker.pack(chunksFromQuery2)

PackError

Custom error class thrown for invalid configurations.

classPackErrorextendsError{readonlycode: stringreadonlydetails?: Record<string,unknown>}

Error codes:

CodeCondition
INVALID_BUDGETbudget is zero or negative.
MISSING_CUSTOM_STRATEGYstrategy is 'custom' but no customStrategy function was provided.
INVALID_STRATEGYAn unknown strategy value was provided.
DIMENSION_MISMATCHEmbedding vectors have different dimensions in cosine similarity.
MISSING_EMBEDDINGSsimilarityMetric is 'cosine' but one or both chunks lack embeddings.

ScoredChunk

Input chunk interface representing a retrieved document segment.

interfaceScoredChunk{content: string// The chunk textscore: number// Relevance score (typically 0-1, higher is better)id?: string// Unique identifier (auto-generated as "chunk-N" if omitted)tokens?: number// Pre-computed token count (skips token counting if provided)embedding?: number[]// Embedding vector (enables cosine similarity for MMR/dedup)metadata?: Record<string,unknown>// Arbitrary metadata (e.g., sourceId, timestamp, url)}

PackedChunk

Output chunk interface for each selected chunk in the result.

interfacePackedChunk{id: string// Chunk identifiercontent: string// The chunk textscore: number// Original relevance scoretokens: number// Token countposition: number// Zero-based position in the ordered outputmetadata?: Record<string,unknown>// Preserved metadata from the input chunk}

ExcludedChunk

Describes a chunk that was not selected, along with the reason for exclusion.

interfaceExcludedChunk{id: stringcontent: stringscore: numbertokens: numberreason: 'budget'|'redundant'|'strategy'|'max-candidates'redundantWith?: string// ID of the chunk this was redundant withsimilarity?: number// Similarity score that triggered redundancy exclusionmetadata?: Record<string,unknown>}

Exclusion reasons:

ReasonDescription
budgetChunk did not fit within the remaining token budget.
redundantChunk exceeded the similarity threshold compared to a higher-scored chunk.
strategyChunk was excluded by the selection strategy.
max-candidatesChunk was beyond the maxCandidates cutoff.

PackOptions

Full configuration interface for pack() and createPacker().

interfacePackOptions{budget: numberstrategy?: 'greedy'|'mmr'|'knapsack'|'custom'lambda?: numberordering?: 'natural'|'u-shaped'|'chronological'redundancyThreshold?: numbersimilarityMetric?: 'auto'|'cosine'|'jaccard'chunkOverheadTokens?: numbertokenCounter?: (text: string)=>numbermaxCandidates?: numbercustomStrategy?: (chunks: ScoredChunk[],ctx: StrategyContext)=>ScoredChunk[]}
OptionTypeDefaultDescription
budgetnumberrequiredMaximum total tokens for the packed context.
strategystring'greedy'Selection strategy. One of 'greedy', 'mmr', 'knapsack', 'custom'.
lambdanumber0.5MMR trade-off parameter. 1.0 = pure relevance, 0.0 = pure diversity.
orderingstring'natural'Output ordering strategy. One of 'natural', 'u-shaped', 'chronological'.
redundancyThresholdnumberundefinedSimilarity threshold for deduplication. Chunks with similarity >= threshold are removed. Set to 1.0 or omit to disable.
similarityMetricstring'auto'Similarity function. 'auto' uses cosine when embeddings are present, Jaccard otherwise.
chunkOverheadTokensnumber0Extra tokens charged per chunk (separators, citation markers, etc.).
tokenCounterfunctionMath.ceil(text.length / 4)Custom token counting function.
maxCandidatesnumberundefinedLimit the number of input chunks considered. Excess chunks are excluded with reason 'max-candidates'.
customStrategyfunctionundefinedRequired when strategy is 'custom'. Receives candidates and a StrategyContext, returns selected chunks.

StrategyContext

Context object passed to custom strategy functions.

interfaceStrategyContext{budget: number// Token budgetchunkOverheadTokens: number// Per-chunk overheadcountTokens: (text: string)=>number// Active token counter functionoptions: PackOptions// Full options object}

PackReport

Structured report returned with every pack result.

interfacePackReport{tokensUsed: number// Total tokens consumed by selected chunks (including overhead)budget: number// The token budget that was providedtokensRemaining: number// budget - tokensUsedutilization: number// tokensUsed / budget (range 0-1)selectedCount: number// Number of chunks selectedexcludedCount: number// Number of chunks excludedstrategy: string// Strategy that was usedordering: string// Ordering that was appliedexcluded: ExcludedChunk[]// Details on every excluded chunktimestamp: string// ISO 8601 timestamp of the pack operationdurationMs: number// Wall-clock duration in milliseconds}

PackResult

Top-level return type from pack().

interfacePackResult{chunks: PackedChunk[]// Selected and ordered chunksreport: PackReport// Structured packing report}

Configuration

Selection Strategies

Greedy (default)

Sorts chunks by score descending and selects greedily until the budget is full. Time complexity: O(n log n).

constresult=awaitpack(chunks,{budget: 4000})// or explicitly:constresult=awaitpack(chunks,{budget: 4000,strategy: 'greedy'})

MMR (Maximal Marginal Relevance)

Iteratively selects the chunk that maximizes lambda * relevance - (1 - lambda) * maxSimilarityToSelected. Balances relevance and diversity.

constresult=awaitpack(chunks,{budget: 4000,strategy: 'mmr',lambda: 0.7,// 1.0 = pure relevance (greedy-like), 0.0 = pure diversity})

When embeddings are provided on ScoredChunk.embedding, MMR uses cosine similarity for diversity computation. Otherwise it falls back to trigram Jaccard similarity.

Knapsack (0/1 Dynamic Programming)

Solves the 0/1 knapsack problem to maximize total score within the exact token budget. Finds the globally optimal subset, not just the greedy-best.

constresult=awaitpack(chunks,{budget: 4000,strategy: 'knapsack',})

For budgets exceeding 5,000 tokens, the knapsack strategy automatically falls back to greedy to avoid excessive memory and computation costs.

Custom Strategy

Supply your own selection function.

constresult=awaitpack(chunks,{budget: 4000,strategy: 'custom',customStrategy: (candidates,ctx)=>{// Select only high-confidence chunksreturncandidates.filter(c=>c.score>0.8)},})

The customStrategy function receives the full candidate list (after redundancy filtering) and a StrategyContext. It must return the subset of ScoredChunk objects to include.

Ordering Strategies

Natural (default)

Chunks are output in the order determined by the selection strategy (score descending for greedy).

U-Shaped

Places the highest-relevance chunks at the beginning and end of the context, with lower-relevance chunks in the middle. This counters the "lost-in-the-middle" effect where LLMs underweight information in the middle of long contexts.

constresult=awaitpack(chunks,{budget: 4000,ordering: 'u-shaped'})

Chronological

Sorts chunks by metadata.timestamp ascending. Useful for time-sensitive contexts where temporal order matters.

constresult=awaitpack(chunks,{budget: 4000,ordering: 'chronological'})

Requires metadata.timestamp (numeric) on each chunk.

Token Counting

The built-in token counter uses Math.ceil(text.length / 4) as a fast approximation suitable for GPT-family models. For exact counts, provide a custom counter:

import{encode}from'gpt-tokenizer'constresult=awaitpack(chunks,{budget: 4000,tokenCounter: (text)=>encode(text).length,})

You can also pre-compute token counts by setting tokens on each ScoredChunk, which bypasses the counter entirely.

Chunk Overhead

Account for per-chunk formatting overhead (separators, citation markers, XML tags) with chunkOverheadTokens:

constresult=awaitpack(chunks,{budget: 4000,chunkOverheadTokens: 10,// 10 extra tokens per chunk for formatting})

The overhead is added to each chunk's token count during both budget calculations and report metrics.


Error Handling

context-packer throws PackError instances for invalid configurations. Each error includes a machine-readable code and an optional details object.

import{pack,PackError}from'context-packer'try{awaitpack(chunks,{budget: -1})}catch(err){if(errinstanceofPackError){console.error(err.code)// 'INVALID_BUDGET'console.error(err.message)// 'Budget must be positive'console.error(err.details)// undefined (or additional context)}}

Error Codes

CodeThrown When
INVALID_BUDGETbudget is zero or negative.
MISSING_CUSTOM_STRATEGYstrategy is 'custom' and customStrategy is not provided.
INVALID_STRATEGYAn unknown strategy value was provided.
DIMENSION_MISMATCHEmbedding vectors have different dimensions in cosine similarity.
MISSING_EMBEDDINGSsimilarityMetric is 'cosine' but one or both chunks lack embeddings.

When no chunks fit the budget, pack returns an empty chunks array with a valid PackReport rather than throwing. Check report.selectedCount === 0 to detect this case.


Advanced Usage

Redundancy Deduplication

Remove near-duplicate chunks before selection to maximize information density:

constresult=awaitpack(chunks,{budget: 4000,redundancyThreshold: 0.85,similarityMetric: 'auto',})

Chunks sorted by score descending are compared pairwise against already-confirmed chunks. If a chunk's similarity to any confirmed chunk meets or exceeds the threshold, it is excluded with reason 'redundant'. The excluded entry includes redundantWith (the ID of the similar chunk) and similarity (the computed score).

Set redundancyThreshold to 1.0 or omit it to disable deduplication.

Similarity metrics:

  • 'auto' (default) -- Uses cosine similarity when both chunks have embedding vectors, Jaccard trigram similarity otherwise.
  • 'cosine' -- Forces cosine similarity. Falls back to Jaccard if embeddings are missing.
  • 'jaccard' -- Always uses trigram Jaccard similarity over chunk text content.

Embedding-Based Diversity

For best results with MMR or redundancy filtering, provide embedding vectors:

constchunks: ScoredChunk[]=[{content: 'Document about authentication',score: 0.92,tokens: 50,embedding: [0.12,0.45,0.78,/* ... */],},{content: 'Document about authorization',score: 0.85,tokens: 40,embedding: [0.11,0.43,0.80,/* ... */],},]constresult=awaitpack(chunks,{budget: 4000,strategy: 'mmr',lambda: 0.6,redundancyThreshold: 0.9,})

Limiting Candidates

When working with large candidate sets, use maxCandidates to cap the number of chunks considered:

constresult=awaitpack(largeChunkSet,{budget: 4000,maxCandidates: 50,// Only consider the first 50 chunks})

Chunks beyond the limit are excluded with reason 'max-candidates' and appear in report.excluded.

Inspecting the Pack Report

The PackReport provides full transparency into packing decisions:

const{chunks: packed, report }=awaitpack(candidates,{budget: 4000,strategy: 'mmr',lambda: 0.7,redundancyThreshold: 0.85,})console.log(`Strategy: ${report.strategy}`)console.log(`Ordering: ${report.ordering}`)console.log(`Utilization: ${(report.utilization*100).toFixed(1)}%`)console.log(`Selected: ${report.selectedCount}, Excluded: ${report.excludedCount}`)console.log(`Duration: ${report.durationMs}ms`)for(constexofreport.excluded){console.log(` Excluded "${ex.id}": ${ex.reason}`)if(ex.reason==='redundant'){console.log(` Redundant with: ${ex.redundantWith} (similarity: ${ex.similarity})`)}}

Combining Strategies with Ordering

Pair any selection strategy with any ordering strategy:

// MMR selection with U-shaped ordering for maximum qualityconstresult=awaitpack(chunks,{budget: 4000,strategy: 'mmr',lambda: 0.6,ordering: 'u-shaped',redundancyThreshold: 0.85,chunkOverheadTokens: 5,})

TypeScript

context-packer is written in TypeScript and ships with declaration files. All public types are exported from the package entry point:

import{pack,createPacker,PackError}from'context-packer'importtype{ScoredChunk,PackedChunk,ExcludedChunk,PackOptions,StrategyContext,PackReport,PackResult,}from'context-packer'

The package targets ES2022 and compiles to CommonJS. Declaration maps are included for IDE navigation into source types.


License

MIT

About

Optimally pack retrieved chunks into an LLM context window

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

context-packer

Budget-aware, diversity-maximizing chunk packing for LLM context windows.

npm versionnpm downloadslicensenode


Description

context-packer selects and arranges the optimal subset of retrieved chunks to fit within a fixed token budget. Every RAG pipeline must decide which chunks to include, how many tokens they consume, and in what order to place them. This package solves all three problems in a single API call.

The library provides multiple selection strategies (greedy, MMR, knapsack, custom), redundancy deduplication via configurable similarity thresholds, and positional reordering to counter the "lost-in-the-middle" effect documented by Liu et al. (2023). Every call returns a structured PackReport explaining exactly which chunks were selected or excluded and why.

Zero runtime dependencies. Written in TypeScript with full type exports.


Installation

npm install context-packer

Requires Node.js 18 or later.


Quick Start

import{pack}from'context-packer'importtype{ScoredChunk}from'context-packer'constchunks: ScoredChunk[]=[{content: 'Relevant document about authentication',score: 0.92,tokens: 50},{content: 'Relevant document about authorization',score: 0.85,tokens: 40},{content: 'Tangentially related document',score: 0.61,tokens: 80},]const{chunks: packed, report }=awaitpack(chunks,{budget: 100})console.log(report.selectedCount)// 2console.log(report.tokensUsed)// 90console.log(report.tokensRemaining)// 10console.log(report.utilization)// 0.9

Features

  • Multiple selection strategies -- Greedy, Maximal Marginal Relevance (MMR), 0/1 knapsack dynamic programming, and custom strategy support.
  • Redundancy deduplication -- Filters near-duplicate chunks before selection using trigram Jaccard similarity or cosine similarity over embedding vectors.
  • Positional reordering -- U-shaped ordering places high-relevance chunks at the beginning and end of the context, countering the lost-in-the-middle effect.
  • Hard token budget enforcement -- Total token count of selected chunks never exceeds the budget, inclusive of configurable per-chunk overhead.
  • Pluggable token counter -- Ships with a character-based approximation (Math.ceil(text.length / 4)). Swap in tiktoken, gpt-tokenizer, or any other counter.
  • Structured pack reports -- Every call returns a PackReport with utilization metrics, excluded chunk reasons, timing, and strategy metadata.
  • Factory pattern -- createPacker produces a reusable packer instance with fixed configuration for repeated use across queries.
  • Zero runtime dependencies -- No production dependencies to audit or maintain.
  • Full TypeScript support -- Ships with declaration files and source maps.

API Reference

pack(chunks, options)

Selects and orders the best subset of chunks that fit within the token budget.

functionpack(chunks: ScoredChunk[],options: PackOptions): Promise<PackResult>

Parameters:

ParameterTypeDescription
chunksScoredChunk[]Array of retrieved chunks with relevance scores.
optionsPackOptionsPacking configuration including budget, strategy, and ordering.

Returns:Promise<PackResult> containing the selected chunks array and a report.

Example:

constresult=awaitpack(chunks,{budget: 4000,strategy: 'mmr',lambda: 0.7,ordering: 'u-shaped',redundancyThreshold: 0.85,})

createPacker(config)

Creates a reusable packer instance with fixed configuration.

functioncreatePacker(config: PackOptions): {pack: (chunks: ScoredChunk[])=>Promise<PackResult>}

Parameters:

ParameterTypeDescription
configPackOptionsFixed packing configuration applied to every call.

Returns: An object with a pack method that accepts only a ScoredChunk[] array.

Example:

constpacker=createPacker({budget: 4000,strategy: 'mmr',lambda: 0.7})constresult1=awaitpacker.pack(chunksFromQuery1)constresult2=awaitpacker.pack(chunksFromQuery2)

PackError

Custom error class thrown for invalid configurations.

classPackErrorextendsError{readonlycode: stringreadonlydetails?: Record<string,unknown>}

Error codes:

CodeCondition
INVALID_BUDGETbudget is zero or negative.
MISSING_CUSTOM_STRATEGYstrategy is 'custom' but no customStrategy function was provided.
INVALID_STRATEGYAn unknown strategy value was provided.
DIMENSION_MISMATCHEmbedding vectors have different dimensions in cosine similarity.
MISSING_EMBEDDINGSsimilarityMetric is 'cosine' but one or both chunks lack embeddings.

ScoredChunk

Input chunk interface representing a retrieved document segment.

interfaceScoredChunk{content: string// The chunk textscore: number// Relevance score (typically 0-1, higher is better)id?: string// Unique identifier (auto-generated as "chunk-N" if omitted)tokens?: number// Pre-computed token count (skips token counting if provided)embedding?: number[]// Embedding vector (enables cosine similarity for MMR/dedup)metadata?: Record<string,unknown>// Arbitrary metadata (e.g., sourceId, timestamp, url)}

PackedChunk

Output chunk interface for each selected chunk in the result.

interfacePackedChunk{id: string// Chunk identifiercontent: string// The chunk textscore: number// Original relevance scoretokens: number// Token countposition: number// Zero-based position in the ordered outputmetadata?: Record<string,unknown>// Preserved metadata from the input chunk}

ExcludedChunk

Describes a chunk that was not selected, along with the reason for exclusion.

interfaceExcludedChunk{id: stringcontent: stringscore: numbertokens: numberreason: 'budget'|'redundant'|'strategy'|'max-candidates'redundantWith?: string// ID of the chunk this was redundant withsimilarity?: number// Similarity score that triggered redundancy exclusionmetadata?: Record<string,unknown>}

Exclusion reasons:

ReasonDescription
budgetChunk did not fit within the remaining token budget.
redundantChunk exceeded the similarity threshold compared to a higher-scored chunk.
strategyChunk was excluded by the selection strategy.
max-candidatesChunk was beyond the maxCandidates cutoff.

PackOptions

Full configuration interface for pack() and createPacker().

interfacePackOptions{budget: numberstrategy?: 'greedy'|'mmr'|'knapsack'|'custom'lambda?: numberordering?: 'natural'|'u-shaped'|'chronological'redundancyThreshold?: numbersimilarityMetric?: 'auto'|'cosine'|'jaccard'chunkOverheadTokens?: numbertokenCounter?: (text: string)=>numbermaxCandidates?: numbercustomStrategy?: (chunks: ScoredChunk[],ctx: StrategyContext)=>ScoredChunk[]}
OptionTypeDefaultDescription
budgetnumberrequiredMaximum total tokens for the packed context.
strategystring'greedy'Selection strategy. One of 'greedy', 'mmr', 'knapsack', 'custom'.
lambdanumber0.5MMR trade-off parameter. 1.0 = pure relevance, 0.0 = pure diversity.
orderingstring'natural'Output ordering strategy. One of 'natural', 'u-shaped', 'chronological'.
redundancyThresholdnumberundefinedSimilarity threshold for deduplication. Chunks with similarity >= threshold are removed. Set to 1.0 or omit to disable.
similarityMetricstring'auto'Similarity function. 'auto' uses cosine when embeddings are present, Jaccard otherwise.
chunkOverheadTokensnumber0Extra tokens charged per chunk (separators, citation markers, etc.).
tokenCounterfunctionMath.ceil(text.length / 4)Custom token counting function.
maxCandidatesnumberundefinedLimit the number of input chunks considered. Excess chunks are excluded with reason 'max-candidates'.
customStrategyfunctionundefinedRequired when strategy is 'custom'. Receives candidates and a StrategyContext, returns selected chunks.

StrategyContext

Context object passed to custom strategy functions.

interfaceStrategyContext{budget: number// Token budgetchunkOverheadTokens: number// Per-chunk overheadcountTokens: (text: string)=>number// Active token counter functionoptions: PackOptions// Full options object}

PackReport

Structured report returned with every pack result.

interfacePackReport{tokensUsed: number// Total tokens consumed by selected chunks (including overhead)budget: number// The token budget that was providedtokensRemaining: number// budget - tokensUsedutilization: number// tokensUsed / budget (range 0-1)selectedCount: number// Number of chunks selectedexcludedCount: number// Number of chunks excludedstrategy: string// Strategy that was usedordering: string// Ordering that was appliedexcluded: ExcludedChunk[]// Details on every excluded chunktimestamp: string// ISO 8601 timestamp of the pack operationdurationMs: number// Wall-clock duration in milliseconds}

PackResult

Top-level return type from pack().

interfacePackResult{chunks: PackedChunk[]// Selected and ordered chunksreport: PackReport// Structured packing report}

Configuration

Selection Strategies

Greedy (default)

Sorts chunks by score descending and selects greedily until the budget is full. Time complexity: O(n log n).

constresult=awaitpack(chunks,{budget: 4000})// or explicitly:constresult=awaitpack(chunks,{budget: 4000,strategy: 'greedy'})

MMR (Maximal Marginal Relevance)

Iteratively selects the chunk that maximizes lambda * relevance - (1 - lambda) * maxSimilarityToSelected. Balances relevance and diversity.

constresult=awaitpack(chunks,{budget: 4000,strategy: 'mmr',lambda: 0.7,// 1.0 = pure relevance (greedy-like), 0.0 = pure diversity})

When embeddings are provided on ScoredChunk.embedding, MMR uses cosine similarity for diversity computation. Otherwise it falls back to trigram Jaccard similarity.

Knapsack (0/1 Dynamic Programming)

Solves the 0/1 knapsack problem to maximize total score within the exact token budget. Finds the globally optimal subset, not just the greedy-best.

constresult=awaitpack(chunks,{budget: 4000,strategy: 'knapsack',})

For budgets exceeding 5,000 tokens, the knapsack strategy automatically falls back to greedy to avoid excessive memory and computation costs.

Custom Strategy

Supply your own selection function.

constresult=awaitpack(chunks,{budget: 4000,strategy: 'custom',customStrategy: (candidates,ctx)=>{// Select only high-confidence chunksreturncandidates.filter(c=>c.score>0.8)},})

The customStrategy function receives the full candidate list (after redundancy filtering) and a StrategyContext. It must return the subset of ScoredChunk objects to include.

Ordering Strategies

Natural (default)

Chunks are output in the order determined by the selection strategy (score descending for greedy).

U-Shaped

Places the highest-relevance chunks at the beginning and end of the context, with lower-relevance chunks in the middle. This counters the "lost-in-the-middle" effect where LLMs underweight information in the middle of long contexts.

constresult=awaitpack(chunks,{budget: 4000,ordering: 'u-shaped'})

Chronological

Sorts chunks by metadata.timestamp ascending. Useful for time-sensitive contexts where temporal order matters.

constresult=awaitpack(chunks,{budget: 4000,ordering: 'chronological'})

Requires metadata.timestamp (numeric) on each chunk.

Token Counting

The built-in token counter uses Math.ceil(text.length / 4) as a fast approximation suitable for GPT-family models. For exact counts, provide a custom counter:

import{encode}from'gpt-tokenizer'constresult=awaitpack(chunks,{budget: 4000,tokenCounter: (text)=>encode(text).length,})

You can also pre-compute token counts by setting tokens on each ScoredChunk, which bypasses the counter entirely.

Chunk Overhead

Account for per-chunk formatting overhead (separators, citation markers, XML tags) with chunkOverheadTokens:

constresult=awaitpack(chunks,{budget: 4000,chunkOverheadTokens: 10,// 10 extra tokens per chunk for formatting})

The overhead is added to each chunk's token count during both budget calculations and report metrics.


Error Handling

context-packer throws PackError instances for invalid configurations. Each error includes a machine-readable code and an optional details object.

import{pack,PackError}from'context-packer'try{awaitpack(chunks,{budget: -1})}catch(err){if(errinstanceofPackError){console.error(err.code)// 'INVALID_BUDGET'console.error(err.message)// 'Budget must be positive'console.error(err.details)// undefined (or additional context)}}

Error Codes

CodeThrown When
INVALID_BUDGETbudget is zero or negative.
MISSING_CUSTOM_STRATEGYstrategy is 'custom' and customStrategy is not provided.
INVALID_STRATEGYAn unknown strategy value was provided.
DIMENSION_MISMATCHEmbedding vectors have different dimensions in cosine similarity.
MISSING_EMBEDDINGSsimilarityMetric is 'cosine' but one or both chunks lack embeddings.

When no chunks fit the budget, pack returns an empty chunks array with a valid PackReport rather than throwing. Check report.selectedCount === 0 to detect this case.


Advanced Usage

Redundancy Deduplication

Remove near-duplicate chunks before selection to maximize information density:

constresult=awaitpack(chunks,{budget: 4000,redundancyThreshold: 0.85,similarityMetric: 'auto',})

Chunks sorted by score descending are compared pairwise against already-confirmed chunks. If a chunk's similarity to any confirmed chunk meets or exceeds the threshold, it is excluded with reason 'redundant'. The excluded entry includes redundantWith (the ID of the similar chunk) and similarity (the computed score).

Set redundancyThreshold to 1.0 or omit it to disable deduplication.

Similarity metrics:

  • 'auto' (default) -- Uses cosine similarity when both chunks have embedding vectors, Jaccard trigram similarity otherwise.
  • 'cosine' -- Forces cosine similarity. Falls back to Jaccard if embeddings are missing.
  • 'jaccard' -- Always uses trigram Jaccard similarity over chunk text content.

Embedding-Based Diversity

For best results with MMR or redundancy filtering, provide embedding vectors:

constchunks: ScoredChunk[]=[{content: 'Document about authentication',score: 0.92,tokens: 50,embedding: [0.12,0.45,0.78,/* ... */],},{content: 'Document about authorization',score: 0.85,tokens: 40,embedding: [0.11,0.43,0.80,/* ... */],},]constresult=awaitpack(chunks,{budget: 4000,strategy: 'mmr',lambda: 0.6,redundancyThreshold: 0.9,})

Limiting Candidates

When working with large candidate sets, use maxCandidates to cap the number of chunks considered:

constresult=awaitpack(largeChunkSet,{budget: 4000,maxCandidates: 50,// Only consider the first 50 chunks})

Chunks beyond the limit are excluded with reason 'max-candidates' and appear in report.excluded.

Inspecting the Pack Report

The PackReport provides full transparency into packing decisions:

const{chunks: packed, report }=awaitpack(candidates,{budget: 4000,strategy: 'mmr',lambda: 0.7,redundancyThreshold: 0.85,})console.log(`Strategy: ${report.strategy}`)console.log(`Ordering: ${report.ordering}`)console.log(`Utilization: ${(report.utilization*100).toFixed(1)}%`)console.log(`Selected: ${report.selectedCount}, Excluded: ${report.excludedCount}`)console.log(`Duration: ${report.durationMs}ms`)for(constexofreport.excluded){console.log(` Excluded "${ex.id}": ${ex.reason}`)if(ex.reason==='redundant'){console.log(` Redundant with: ${ex.redundantWith} (similarity: ${ex.similarity})`)}}

Combining Strategies with Ordering

Pair any selection strategy with any ordering strategy:

// MMR selection with U-shaped ordering for maximum qualityconstresult=awaitpack(chunks,{budget: 4000,strategy: 'mmr',lambda: 0.6,ordering: 'u-shaped',redundancyThreshold: 0.85,chunkOverheadTokens: 5,})

TypeScript

context-packer is written in TypeScript and ships with declaration files. All public types are exported from the package entry point:

import{pack,createPacker,PackError}from'context-packer'importtype{ScoredChunk,PackedChunk,ExcludedChunk,PackOptions,StrategyContext,PackReport,PackResult,}from'context-packer'

The package targets ES2022 and compiles to CommonJS. Declaration maps are included for IDE navigation into source types.


License

MIT

About

Optimally pack retrieved chunks into an LLM context window

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

context-packer

Budget-aware, diversity-maximizing chunk packing for LLM context windows.

npm versionnpm downloadslicensenode


Description

context-packer selects and arranges the optimal subset of retrieved chunks to fit within a fixed token budget. Every RAG pipeline must decide which chunks to include, how many tokens they consume, and in what order to place them. This package solves all three problems in a single API call.

The library provides multiple selection strategies (greedy, MMR, knapsack, custom), redundancy deduplication via configurable similarity thresholds, and positional reordering to counter the "lost-in-the-middle" effect documented by Liu et al. (2023). Every call returns a structured PackReport explaining exactly which chunks were selected or excluded and why.

Zero runtime dependencies. Written in TypeScript with full type exports.


Installation

npm install context-packer

Requires Node.js 18 or later.


Quick Start

import{pack}from'context-packer'importtype{ScoredChunk}from'context-packer'constchunks: ScoredChunk[]=[{content: 'Relevant document about authentication',score: 0.92,tokens: 50},{content: 'Relevant document about authorization',score: 0.85,tokens: 40},{content: 'Tangentially related document',score: 0.61,tokens: 80},]const{chunks: packed, report }=awaitpack(chunks,{budget: 100})console.log(report.selectedCount)// 2console.log(report.tokensUsed)// 90console.log(report.tokensRemaining)// 10console.log(report.utilization)// 0.9

Features

  • Multiple selection strategies -- Greedy, Maximal Marginal Relevance (MMR), 0/1 knapsack dynamic programming, and custom strategy support.
  • Redundancy deduplication -- Filters near-duplicate chunks before selection using trigram Jaccard similarity or cosine similarity over embedding vectors.
  • Positional reordering -- U-shaped ordering places high-relevance chunks at the beginning and end of the context, countering the lost-in-the-middle effect.
  • Hard token budget enforcement -- Total token count of selected chunks never exceeds the budget, inclusive of configurable per-chunk overhead.
  • Pluggable token counter -- Ships with a character-based approximation (Math.ceil(text.length / 4)). Swap in tiktoken, gpt-tokenizer, or any other counter.
  • Structured pack reports -- Every call returns a PackReport with utilization metrics, excluded chunk reasons, timing, and strategy metadata.
  • Factory pattern -- createPacker produces a reusable packer instance with fixed configuration for repeated use across queries.
  • Zero runtime dependencies -- No production dependencies to audit or maintain.
  • Full TypeScript support -- Ships with declaration files and source maps.

API Reference

pack(chunks, options)

Selects and orders the best subset of chunks that fit within the token budget.

functionpack(chunks: ScoredChunk[],options: PackOptions): Promise<PackResult>

Parameters:

ParameterTypeDescription
chunksScoredChunk[]Array of retrieved chunks with relevance scores.
optionsPackOptionsPacking configuration including budget, strategy, and ordering.

Returns:Promise<PackResult> containing the selected chunks array and a report.

Example:

constresult=awaitpack(chunks,{budget: 4000,strategy: 'mmr',lambda: 0.7,ordering: 'u-shaped',redundancyThreshold: 0.85,})

createPacker(config)

Creates a reusable packer instance with fixed configuration.

functioncreatePacker(config: PackOptions): {pack: (chunks: ScoredChunk[])=>Promise<PackResult>}

Parameters:

ParameterTypeDescription
configPackOptionsFixed packing configuration applied to every call.

Returns: An object with a pack method that accepts only a ScoredChunk[] array.

Example:

constpacker=createPacker({budget: 4000,strategy: 'mmr',lambda: 0.7})constresult1=awaitpacker.pack(chunksFromQuery1)constresult2=awaitpacker.pack(chunksFromQuery2)

PackError

Custom error class thrown for invalid configurations.

classPackErrorextendsError{readonlycode: stringreadonlydetails?: Record<string,unknown>}

Error codes:

CodeCondition
INVALID_BUDGETbudget is zero or negative.
MISSING_CUSTOM_STRATEGYstrategy is 'custom' but no customStrategy function was provided.
INVALID_STRATEGYAn unknown strategy value was provided.
DIMENSION_MISMATCHEmbedding vectors have different dimensions in cosine similarity.
MISSING_EMBEDDINGSsimilarityMetric is 'cosine' but one or both chunks lack embeddings.

ScoredChunk

Input chunk interface representing a retrieved document segment.

interfaceScoredChunk{content: string// The chunk textscore: number// Relevance score (typically 0-1, higher is better)id?: string// Unique identifier (auto-generated as "chunk-N" if omitted)tokens?: number// Pre-computed token count (skips token counting if provided)embedding?: number[]// Embedding vector (enables cosine similarity for MMR/dedup)metadata?: Record<string,unknown>// Arbitrary metadata (e.g., sourceId, timestamp, url)}

PackedChunk

Output chunk interface for each selected chunk in the result.

interfacePackedChunk{id: string// Chunk identifiercontent: string// The chunk textscore: number// Original relevance scoretokens: number// Token countposition: number// Zero-based position in the ordered outputmetadata?: Record<string,unknown>// Preserved metadata from the input chunk}

ExcludedChunk

Describes a chunk that was not selected, along with the reason for exclusion.

interfaceExcludedChunk{id: stringcontent: stringscore: numbertokens: numberreason: 'budget'|'redundant'|'strategy'|'max-candidates'redundantWith?: string// ID of the chunk this was redundant withsimilarity?: number// Similarity score that triggered redundancy exclusionmetadata?: Record<string,unknown>}

Exclusion reasons:

ReasonDescription
budgetChunk did not fit within the remaining token budget.
redundantChunk exceeded the similarity threshold compared to a higher-scored chunk.
strategyChunk was excluded by the selection strategy.
max-candidatesChunk was beyond the maxCandidates cutoff.

PackOptions

Full configuration interface for pack() and createPacker().

interfacePackOptions{budget: numberstrategy?: 'greedy'|'mmr'|'knapsack'|'custom'lambda?: numberordering?: 'natural'|'u-shaped'|'chronological'redundancyThreshold?: numbersimilarityMetric?: 'auto'|'cosine'|'jaccard'chunkOverheadTokens?: numbertokenCounter?: (text: string)=>numbermaxCandidates?: numbercustomStrategy?: (chunks: ScoredChunk[],ctx: StrategyContext)=>ScoredChunk[]}
OptionTypeDefaultDescription
budgetnumberrequiredMaximum total tokens for the packed context.
strategystring'greedy'Selection strategy. One of 'greedy', 'mmr', 'knapsack', 'custom'.
lambdanumber0.5MMR trade-off parameter. 1.0 = pure relevance, 0.0 = pure diversity.
orderingstring'natural'Output ordering strategy. One of 'natural', 'u-shaped', 'chronological'.
redundancyThresholdnumberundefinedSimilarity threshold for deduplication. Chunks with similarity >= threshold are removed. Set to 1.0 or omit to disable.
similarityMetricstring'auto'Similarity function. 'auto' uses cosine when embeddings are present, Jaccard otherwise.
chunkOverheadTokensnumber0Extra tokens charged per chunk (separators, citation markers, etc.).
tokenCounterfunctionMath.ceil(text.length / 4)Custom token counting function.
maxCandidatesnumberundefinedLimit the number of input chunks considered. Excess chunks are excluded with reason 'max-candidates'.
customStrategyfunctionundefinedRequired when strategy is 'custom'. Receives candidates and a StrategyContext, returns selected chunks.

StrategyContext

Context object passed to custom strategy functions.

interfaceStrategyContext{budget: number// Token budgetchunkOverheadTokens: number// Per-chunk overheadcountTokens: (text: string)=>number// Active token counter functionoptions: PackOptions// Full options object}

PackReport

Structured report returned with every pack result.

interfacePackReport{tokensUsed: number// Total tokens consumed by selected chunks (including overhead)budget: number// The token budget that was providedtokensRemaining: number// budget - tokensUsedutilization: number// tokensUsed / budget (range 0-1)selectedCount: number// Number of chunks selectedexcludedCount: number// Number of chunks excludedstrategy: string// Strategy that was usedordering: string// Ordering that was appliedexcluded: ExcludedChunk[]// Details on every excluded chunktimestamp: string// ISO 8601 timestamp of the pack operationdurationMs: number// Wall-clock duration in milliseconds}

PackResult

Top-level return type from pack().

interfacePackResult{chunks: PackedChunk[]// Selected and ordered chunksreport: PackReport// Structured packing report}

Configuration

Selection Strategies

Greedy (default)

Sorts chunks by score descending and selects greedily until the budget is full. Time complexity: O(n log n).

constresult=awaitpack(chunks,{budget: 4000})// or explicitly:constresult=awaitpack(chunks,{budget: 4000,strategy: 'greedy'})

MMR (Maximal Marginal Relevance)

Iteratively selects the chunk that maximizes lambda * relevance - (1 - lambda) * maxSimilarityToSelected. Balances relevance and diversity.

constresult=awaitpack(chunks,{budget: 4000,strategy: 'mmr',lambda: 0.7,// 1.0 = pure relevance (greedy-like), 0.0 = pure diversity})

When embeddings are provided on ScoredChunk.embedding, MMR uses cosine similarity for diversity computation. Otherwise it falls back to trigram Jaccard similarity.

Knapsack (0/1 Dynamic Programming)

Solves the 0/1 knapsack problem to maximize total score within the exact token budget. Finds the globally optimal subset, not just the greedy-best.

constresult=awaitpack(chunks,{budget: 4000,strategy: 'knapsack',})

For budgets exceeding 5,000 tokens, the knapsack strategy automatically falls back to greedy to avoid excessive memory and computation costs.

Custom Strategy

Supply your own selection function.

constresult=awaitpack(chunks,{budget: 4000,strategy: 'custom',customStrategy: (candidates,ctx)=>{// Select only high-confidence chunksreturncandidates.filter(c=>c.score>0.8)},})

The customStrategy function receives the full candidate list (after redundancy filtering) and a StrategyContext. It must return the subset of ScoredChunk objects to include.

Ordering Strategies

Natural (default)

Chunks are output in the order determined by the selection strategy (score descending for greedy).

U-Shaped

Places the highest-relevance chunks at the beginning and end of the context, with lower-relevance chunks in the middle. This counters the "lost-in-the-middle" effect where LLMs underweight information in the middle of long contexts.

constresult=awaitpack(chunks,{budget: 4000,ordering: 'u-shaped'})

Chronological

Sorts chunks by metadata.timestamp ascending. Useful for time-sensitive contexts where temporal order matters.

constresult=awaitpack(chunks,{budget: 4000,ordering: 'chronological'})

Requires metadata.timestamp (numeric) on each chunk.

Token Counting

The built-in token counter uses Math.ceil(text.length / 4) as a fast approximation suitable for GPT-family models. For exact counts, provide a custom counter:

import{encode}from'gpt-tokenizer'constresult=awaitpack(chunks,{budget: 4000,tokenCounter: (text)=>encode(text).length,})

You can also pre-compute token counts by setting tokens on each ScoredChunk, which bypasses the counter entirely.

Chunk Overhead

Account for per-chunk formatting overhead (separators, citation markers, XML tags) with chunkOverheadTokens:

constresult=awaitpack(chunks,{budget: 4000,chunkOverheadTokens: 10,// 10 extra tokens per chunk for formatting})

The overhead is added to each chunk's token count during both budget calculations and report metrics.


Error Handling

context-packer throws PackError instances for invalid configurations. Each error includes a machine-readable code and an optional details object.

import{pack,PackError}from'context-packer'try{awaitpack(chunks,{budget: -1})}catch(err){if(errinstanceofPackError){console.error(err.code)// 'INVALID_BUDGET'console.error(err.message)// 'Budget must be positive'console.error(err.details)// undefined (or additional context)}}

Error Codes

CodeThrown When
INVALID_BUDGETbudget is zero or negative.
MISSING_CUSTOM_STRATEGYstrategy is 'custom' and customStrategy is not provided.
INVALID_STRATEGYAn unknown strategy value was provided.
DIMENSION_MISMATCHEmbedding vectors have different dimensions in cosine similarity.
MISSING_EMBEDDINGSsimilarityMetric is 'cosine' but one or both chunks lack embeddings.

When no chunks fit the budget, pack returns an empty chunks array with a valid PackReport rather than throwing. Check report.selectedCount === 0 to detect this case.


Advanced Usage

Redundancy Deduplication

Remove near-duplicate chunks before selection to maximize information density:

constresult=awaitpack(chunks,{budget: 4000,redundancyThreshold: 0.85,similarityMetric: 'auto',})

Chunks sorted by score descending are compared pairwise against already-confirmed chunks. If a chunk's similarity to any confirmed chunk meets or exceeds the threshold, it is excluded with reason 'redundant'. The excluded entry includes redundantWith (the ID of the similar chunk) and similarity (the computed score).

Set redundancyThreshold to 1.0 or omit it to disable deduplication.

Similarity metrics:

  • 'auto' (default) -- Uses cosine similarity when both chunks have embedding vectors, Jaccard trigram similarity otherwise.
  • 'cosine' -- Forces cosine similarity. Falls back to Jaccard if embeddings are missing.
  • 'jaccard' -- Always uses trigram Jaccard similarity over chunk text content.

Embedding-Based Diversity

For best results with MMR or redundancy filtering, provide embedding vectors:

constchunks: ScoredChunk[]=[{content: 'Document about authentication',score: 0.92,tokens: 50,embedding: [0.12,0.45,0.78,/* ... */],},{content: 'Document about authorization',score: 0.85,tokens: 40,embedding: [0.11,0.43,0.80,/* ... */],},]constresult=awaitpack(chunks,{budget: 4000,strategy: 'mmr',lambda: 0.6,redundancyThreshold: 0.9,})

Limiting Candidates

When working with large candidate sets, use maxCandidates to cap the number of chunks considered:

constresult=awaitpack(largeChunkSet,{budget: 4000,maxCandidates: 50,// Only consider the first 50 chunks})

Chunks beyond the limit are excluded with reason 'max-candidates' and appear in report.excluded.

Inspecting the Pack Report

The PackReport provides full transparency into packing decisions:

const{chunks: packed, report }=awaitpack(candidates,{budget: 4000,strategy: 'mmr',lambda: 0.7,redundancyThreshold: 0.85,})console.log(`Strategy: ${report.strategy}`)console.log(`Ordering: ${report.ordering}`)console.log(`Utilization: ${(report.utilization*100).toFixed(1)}%`)console.log(`Selected: ${report.selectedCount}, Excluded: ${report.excludedCount}`)console.log(`Duration: ${report.durationMs}ms`)for(constexofreport.excluded){console.log(` Excluded "${ex.id}": ${ex.reason}`)if(ex.reason==='redundant'){console.log(` Redundant with: ${ex.redundantWith} (similarity: ${ex.similarity})`)}}

Combining Strategies with Ordering

Pair any selection strategy with any ordering strategy:

// MMR selection with U-shaped ordering for maximum qualityconstresult=awaitpack(chunks,{budget: 4000,strategy: 'mmr',lambda: 0.6,ordering: 'u-shaped',redundancyThreshold: 0.85,chunkOverheadTokens: 5,})

TypeScript

context-packer is written in TypeScript and ships with declaration files. All public types are exported from the package entry point:

import{pack,createPacker,PackError}from'context-packer'importtype{ScoredChunk,PackedChunk,ExcludedChunk,PackOptions,StrategyContext,PackReport,PackResult,}from'context-packer'

The package targets ES2022 and compiles to CommonJS. Declaration maps are included for IDE navigation into source types.


License

MIT

About

Optimally pack retrieved chunks into an LLM context window

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages