Back to Blog
Lesson 23 of the Advanced AI/ML: Deep Learning, LLMs & Production Systems course
AI/MLJune 28, 20264 min read

Context Management and Windowing: Advanced RAG Strategies

Master Context Management and windowing in RAG pipelines. Learn to implement semantic chunking, optimize indexing, and respect LLM token limits in production.

RAGLong-ContextContext ManagementChunkingLLMVector Searchaimachine-learningpython

Previously in this course, we explored Vector Databases and Similarity Search and Retrieval Strategies for RAG. Now that you have a functional retrieval engine, you must address the bottleneck of feeding that retrieved data into the LLM.

In production, simply stuffing retrieved documents into a prompt is a recipe for hallucinations and cost overruns. This lesson focuses on Context Management and Windowing by shifting from naive character-based splitting to semantic-aware chunking and intelligent context budgeting.

The Problem with Fixed-Size Chunking

Most RAG implementations start with fixed-size chunks (e.g., 512 tokens with 50-token overlap). While easy to implement, this approach frequently splits sentences or paragraphs in half, destroying the semantic cohesion required for the model to "understand" the context.

When you lose semantic boundaries, your embeddings become noisy, and the LLM receives fragmented information. To solve this, we move toward Semantic Chunking.

Implementing Semantic Chunking

Semantic chunking relies on identifying natural breakpoints in text—such as sentence endings or thematic shifts—rather than arbitrary character counts. We can use embedding distance to detect where one topic ends and another begins.

PYTHON
import numpy as np
from sentence_transformers import SentenceTransformer, util

model = SentenceTransformer(CE9178">'all-MiniLM-L6-v2')

def get_semantic_chunks(text, threshold=0.7):
    # Split by sentences(simple heuristic)
    sentences = text.split(CE9178">'. ')
    embeddings = model.encode(sentences)
    
    chunks = []
    current_chunk = [sentences[0]]
    
    for i in range(1, len(embeddings)):
        # Calculate cosine similarity between consecutive sentences
        sim = util.cos_sim(embeddings[i-1], embeddings[i])
        
        if sim < threshold:
            # Semantic shift detected: start new chunk
            chunks.append(". ".join(current_chunk))
            current_chunk = [sentences[i]]
        else:
            current_chunk.append(sentences[i])
            
    chunks.append(". ".join(current_chunk))
    return chunks

This approach ensures that every chunk passed to your retriever represents a cohesive thought, significantly improving the precision of your LLM Cost Control: Mastering Dynamic Context Window Management.

Managing Context Window Limits

Once you have high-quality chunks, you must manage the "fill rate" of your context window. You are balancing two conflicting goals: providing enough evidence to ground the answer and minimizing tokens to reduce latency and cost.

Dynamic Context Budgeting

In our project, we implement a "budget-first" retrieval loop. Instead of retrieving a fixed number of chunks, we retrieve based on a token-budget calculation.

  1. System Prompt Token Count: Reserved upfront.
  2. Dynamic Query Expansion: Account for user-specific constraints.
  3. Chunk Prioritization: Rank chunks by a cross-encoder (see Retrieval Strategies for RAG) and inject them until the budget is hit.

For a deeper dive into preventing UI-level context overflow, review LLM Streaming and Token Management: Preventing UI Context Overflow.

Optimizing Indexing for Retrieval

Efficient retrieval requires that your indexed chunks remain searchable even as your document base grows.

  • Metadata Filtering: Always index chunks with metadata (category, date, document_id). This allows pre-filtering, which effectively reduces the search space before similarity search occurs.
  • Hierarchical Indexing: Index small "summary" chunks for global search and large "source" chunks for local retrieval. This helps when a query spans multiple documents.

Hands-on Exercise: Implementing a Budgeted Retriever

Modify your current retriever to accept a max_tokens argument.

  1. Use the tiktoken library to calculate token counts.
  2. Sort your retrieved chunks by the cross-encoder score.
  3. Iteratively add chunks to the context until current_tokens + chunk_tokens > max_tokens.
  4. If a chunk is too large to fit, implement a "truncation strategy" (e.g., taking the first/last N tokens) or skip it entirely.

Common Pitfalls

  • Ignoring Overlap: Even with semantic chunking, you need a small overlap (e.g., 5-10% of the chunk size) to maintain continuity across chunks.
  • Over-summarization: If you use LLMs to summarize chunks before indexing, ensure you don't lose the "needle in the haystack" details required for specific queries.
  • Hard-coding Limits: Always leave a 10-15% buffer in your context window for the model's output tokens. If the model runs out of space mid-generation, it will truncate its answer.

Recap

We've moved beyond basic chunking to semantic-aware partitioning and dynamic budget management. By balancing retrieval quality with rigid token constraints, you ensure your RAG application remains performant and cost-effective. These techniques, combined with the strategies discussed in LLM Context Window Management: Chunking and Summarization Tips, form the foundation of a robust production system.

Up next: Agentic Tool Use and Function Calling — we’ll teach your model to use external APIs to overcome static context limitations.

Similar Posts