Table of Contents

  1. Introduction: The Long-Context Dilemma in Enterprise RAG

  2. Why Naive Approaches Fail on Long Documents

  3. Five Core Strategies for Long-Context Handling

  4. The Adaptive Answer: Strategy Selection by Document Profile

  5. Solution Architecture: The "Long-Context Orchestrator"

  6. Technology Stack Overview

  7. Step-by-Step Implementation: Backend Development

    • Defining the Long-Context State Schema with Memory

    • Building the Document Profiler Agent

    • Implementing the Strategy Router Agent

    • Creating the Hierarchical Summarizer Agent

    • Designing the Parent-Child Retriever Agent

    • Building the Cross-Section Assembler Agent

    • Constructing the LangGraph Workflow with Conditional Routing

  8. Frontend Implementation: Long-Context Analysis Dashboard

  9. Real-Time Use Case: M&A Contract Intelligence Platform

  10. Conclusion: Matching Strategy to Document

Introduction

Long documents are the silent killer of RAG performance. A 200-page M&A agreement, a 500-page technical manual, or a 1,000-page regulatory filing cannot be processed with naive chunking—semantic units span multiple chunks, cross-references connect distant sections, and critical context gets fragmented across retrieval boundaries. Meanwhile, simply stuffing the entire document into a long-context LLM window (Gemini 1M, Claude 200K) is expensive, slow, and often produces worse results due to "lost-in-the-middle" attention degradation.

The enterprise answer is adaptive strategy selection: different documents and different queries demand different approaches. A factual lookup in a 500-page manual needs precise chunked retrieval. A thematic question about a 200-page contract needs hierarchical summarization. A cross-reference query needs multi-section assembly. This article presents an enterprise-grade multi-agent LangGraph system that profiles each document, selects the optimal long-context strategy, executes it with persistent memory of past decisions, and delivers answers that respect document structure.

Why Naive Approaches Fail

Approach

Failure Mode

Fixed-size chunking

Splits semantic units mid-sentence; loses cross-section references

Large chunks (2000+ tokens)

Retrieval becomes imprecise; irrelevant content drowns signal

Stuff entire document

Expensive ($0.10+ per query); lost-in-the-middle effect; slow

Single-level summarization

Loses detail; cannot answer specific factual questions

Pure vector search

Misses structural hierarchy (chapters → sections → clauses)

Five Core Strategies for Long-Context Handling

1. Hierarchical Summarization (Map-Reduce): Summarize chunks → summarize summaries → answer from global summary. Best for thematic, high-level questions.

2. Parent-Child (Small-to-Big): Retrieve small precise chunks but return their larger parent context. Best for factual QA over long docs.

3. Recursive Section-Aware Chunking: Respect document hierarchy (chapter → section → subsection). Best for structured technical docs.

4. Native Long-Context Stuffing: Pass entire document to 128K+ context model. Best for short docs (<50 pages) or when cost is not a constraint.

5. Iterative Cross-Section Assembly: Identify relevant sections, retrieve each, assemble coherent context. Best for cross-reference queries.

The Adaptive Answer

The key insight: no single strategy dominates. The optimal choice depends on:

  • Document length (pages, tokens)

  • Document structure (headings, sections, hierarchy depth)

  • Query type (factual, thematic, cross-reference, comparative)

  • Latency and cost constraints

Technology Tags

Python, LangGraph, LangChain, FastAPI, React, PostgreSQL, pgvector, Redis, Pydantic, OpenAI API, Sentence Transformers, Unstructured, LlamaIndex, HNSWLib, Docker, TypeScript, TailwindCSS, spaCy, NetworkX

Step-by-Step Implementation

1. Long-Context State Schema with Memory

from typing import List, Dict, Any, TypedDict, Optional, Literal
from langgraph.graph import StateGraph, END
from langchain_core.messages import HumanMessage
import redis
import json
from datetime import datetime

LongContextStrategy = Literal[
    "hierarchical_summary", "parent_child", "section_aware",
    "native_long_context", "cross_section_assembly"
]

class DocumentSection(TypedDict):
    id: str
    title: str
    level: int  # 1=chapter, 2=section, 3=subsection
    content: str
    parent_id: Optional[str]
    children_ids: List[str]
    summary: Optional[str]

class LongContextState(TypedDict):
    messages: List
    conversation_id: str
    document_id: str
    # Document profile
    document_text: str
    token_count: int
    page_count: int
    section_count: int
    hierarchy_depth: int
    has_structure: bool
    sections: List[DocumentSection]
    # Query analysis
    query: str
    query_type: Literal["factual", "thematic", "cross_reference", "comparative"]
    relevant_section_ids: List[str]
    # Strategy selection
    selected_strategy: LongContextStrategy
    strategy_reasoning: str
    # Execution results
    assembled_context: str
    final_answer: str
    citations: List[Dict]
    # Metrics
    context_tokens_used: int
    estimated_cost_usd: float
    latency_ms: float
    # Memory
    past_strategies: List[Dict]
    document_type_history: Dict[str, List[str]]

2. Document Profiler Agent

from langchain_openai import ChatOpenAI
import re

class DocumentProfilerAgent:
    """Analyzes document structure, length, and hierarchy"""
    
    def __init__(self):
        self.llm = ChatOpenAI(model="gpt-4", temperature=0.0)
    
    def profile(self, state: LongContextState) -> LongContextState:
        text = state["document_text"]
        
        # Basic metrics
        state["token_count"] = int(len(text.split()) * 1.3)
        state["page_count"] = max(1, state["token_count"] // 500)
        
        # Detect structure via headings
        heading_pattern = r'^(#{1,6}\s.+|Chapter\s+\d+[:\.]?\s*.+|Section\s+\d+[:\.]?\s*.+|Article\s+\d+[:\.]?\s*.+|[A-Z][^.!?\n]{3,80}\n)$'
        headings = re.findall(heading_pattern, text, re.MULTILINE)
        state["section_count"] = len(headings)
        state["has_structure"] = len(headings) >= 3
        
        # Parse sections hierarchically
        sections = []
        current_section = None
        lines = text.split('\n')
        
        for i, line in enumerate(lines):
            heading_match = re.match(r'^(#{1,6})\s+(.+)', line)
            if heading_match:
                level = len(heading_match.group(1))
                if current_section:
                    sections.append(current_section)
                current_section = {
                    "id": f"sec_{len(sections)}",
                    "title": heading_match.group(2).strip(),
                    "level": level,
                    "content": "",
                    "parent_id": None,
                    "children_ids": [],
                    "summary": None
                }
            elif current_section:
                current_section["content"] += line + "\n"
        
        if current_section:
            sections.append(current_section)
        
        # Build parent-child relationships
        section_stack = []
        for sec in sections:
            while section_stack and section_stack[-1]["level"] >= sec["level"]:
                section_stack.pop()
            if section_stack:
                sec["parent_id"] = section_stack[-1]["id"]
                section_stack[-1]["children_ids"].append(sec["id"])
            section_stack.append(sec)
        
        state["sections"] = sections
        state["hierarchy_depth"] = max((s["level"] for s in sections), default=0)
        
        return state

3. Strategy Router Agent

class StrategyRouterAgent:
    """Selects optimal long-context strategy based on document + query profile"""
    
    def __init__(self, redis_client: redis.Redis):
        self.redis = redis_client
        self.llm = ChatOpenAI(model="gpt-4", temperature=0.0)
    
    def _classify_query(self, query: str) -> str:
        prompt = f"""Classify this query type for long-document retrieval:
Query: "{query}"

Types:
- factual: specific fact, number, name, date lookup
- thematic: high-level theme, summary, overall understanding
- cross_reference: connects multiple sections, compares parts
- comparative: compares entities or concepts across document

Respond with ONE word: factual|thematic|cross_reference|comparative"""
        resp = self.llm.invoke(prompt)
        return resp.content.strip().lower()
    
    def _identify_relevant_sections(self, state: LongContextState) -> List[str]:
        """Use LLM to identify which sections likely contain the answer"""
        section_summaries = "\n".join([
            f"[{s['id']}] (L{s['level']}) {s['title']}: {s['content'][:150]}..."
            for s in state["sections"][:30]
        ])
        
        prompt = f"""Given this document structure, which section IDs are most relevant to answering the query?

Query: "{state['query']}"

Sections:
{section_summaries}

Respond with comma-separated section IDs (max 5):"""
        resp = self.llm.invoke(prompt)
        ids = [s.strip() for s in resp.content.split(",") if s.strip().startswith("sec_")]
        return ids[:5]
    
    def route(self, state: LongContextState) -> LongContextState:
        state["query_type"] = self._classify_query(state["query"])
        state["relevant_section_ids"] = self._identify_relevant_sections(state)
        
        # Decision logic
        token_count = state["token_count"]
        has_structure = state["has_structure"]
        query_type = state["query_type"]
        relevant_count = len(state["relevant_section_ids"])
        
        # Check historical preferences for this document type
        history_key = f"long_context_history:{state['document_id']}"
        history = json.loads(self.redis.get(history_key) or "[]")
        
        # Strategy selection
        if token_count < 30000:
            # Short enough for native long-context
            state["selected_strategy"] = "native_long_context"
            state["strategy_reasoning"] = "Document fits within native context window; direct processing is most accurate"
        elif query_type == "thematic":
            state["selected_strategy"] = "hierarchical_summary"
            state["strategy_reasoning"] = "Thematic query benefits from hierarchical summarization to capture global themes"
        elif query_type in ("cross_reference", "comparative") and relevant_count > 1:
            state["selected_strategy"] = "cross_section_assembly"
            state["strategy_reasoning"] = f"Query spans {relevant_count} sections; assembling cross-section context is optimal"
        elif has_structure and state["hierarchy_depth"] >= 2:
            state["selected_strategy"] = "section_aware"
            state["strategy_reasoning"] = "Document has clear hierarchy; section-aware retrieval preserves structure"
        else:
            state["selected_strategy"] = "parent_child"
            state["strategy_reasoning"] = "Parent-child retrieval balances precision with context richness"
        
        # Record in memory
        history.append({
            "query_type": query_type,
            "strategy": state["selected_strategy"],
            "timestamp": datetime.now().isoformat()
        })
        self.redis.set(history_key, json.dumps(history[-20:]))
        state["past_strategies"] = history
        
        return state

4. Hierarchical Summarizer Agent

class HierarchicalSummarizerAgent:
    """Map-reduce summarization for thematic queries"""
    
    def __init__(self):
        self.llm = ChatOpenAI(model="gpt-4", temperature=0.1)
    
    def summarize(self, state: LongContextState) -> LongContextState:
        sections = state["sections"]
        
        # Map phase: summarize each section
        section_summaries = []
        for sec in sections[:20]:  # Limit for cost
            prompt = f"""Summarize this section in 2-3 sentences, preserving key facts:

Title: {sec['title']}
Content: {sec['content'][:1500]}

Summary:"""
            resp = self.llm.invoke(prompt)
            summary = resp.content.strip()
            sec["summary"] = summary
            section_summaries.append(f"[{sec['title']}]: {summary}")
        
        # Reduce phase: synthesize global summary relevant to query
        combined = "\n".join(section_summaries)
        prompt = f"""Given these section summaries, answer the query comprehensively.

Query: {state['query']}

Section Summaries:
{combined}

Provide a detailed answer citing relevant sections:"""
        resp = self.llm.invoke(prompt)
        
        state["assembled_context"] = combined
        state["final_answer"] = resp.content
        state["context_tokens_used"] = int(len(combined.split()) * 1.3)
        state["citations"] = [{"section": s["title"], "id": s["id"]} for s in sections[:10]]
        
        return state

5. Parent-Child Retriever Agent

from langchain_huggingface import HuggingFaceEmbeddings
from sklearn.metrics.pairwise import cosine_similarity
import numpy as np

class ParentChildRetrieverAgent:
    """Retrieves small chunks but returns parent context"""
    
    def __init__(self):
        self.embeddings = HuggingFaceEmbeddings(
            model_name="sentence-transformers/all-MiniLM-L6-v2"
        )
    
    def retrieve(self, state: LongContextState) -> LongContextState:
        # Build child chunks (paragraphs) with parent references
        children = []
        for sec in state["sections"]:
            paragraphs = [p.strip() for p in sec["content"].split("\n\n") if len(p.strip()) > 50]
            for para in paragraphs:
                children.append({
                    "content": para,
                    "parent_section_id": sec["id"],
                    "parent_title": sec["title"]
                })
        
        if not children:
            state["final_answer"] = "No content available for retrieval."
            return state
        
        # Embed children
        child_texts = [c["content"] for c in children]
        child_embeddings = np.array(self.embeddings.embed_documents(child_texts))
        query_embedding = np.array(self.embeddings.embed_query(state["query"]))
        
        # Retrieve top children
        similarities = cosine_similarity(query_embedding.reshape(1, -1), child_embeddings)[0]
        top_indices = np.argsort(similarities)[::-1][:8]
        
        # Expand to parents
        parent_ids = set()
        retrieved_parents = []
        for idx in top_indices:
            parent_id = children[idx]["parent_section_id"]
            if parent_id not in parent_ids:
                parent_ids.add(parent_id)
                parent_sec = next((s for s in state["sections"] if s["id"] == parent_id), None)
                if parent_sec:
                    retrieved_parents.append(parent_sec)
        
        # Assemble context from parents
        context_parts = []
        for p in retrieved_parents[:5]:
            context_parts.append(f"## {p['title']}\n{p['content'][:1500]}")
        
        assembled = "\n\n---\n\n".join(context_parts)
        state["assembled_context"] = assembled
        state["context_tokens_used"] = int(len(assembled.split()) * 1.3)
        state["citations"] = [{"section": p["title"], "id": p["id"]} for p in retrieved_parents[:5]]
        
        return state

6. Cross-Section Assembler Agent

class CrossSectionAssemblerAgent:
    """Assembles context from multiple relevant sections for cross-reference queries"""
    
    def __init__(self):
        self.llm = ChatOpenAI(model="gpt-4", temperature=0.1)
    
    def assemble(self, state: LongContextState) -> LongContextState:
        relevant_ids = state["relevant_section_ids"]
        sections = [s for s in state["sections"] if s["id"] in relevant_ids]
        
        # Include parent sections for context
        expanded = []
        for sec in sections:
            expanded.append(sec)
            if sec["parent_id"]:
                parent = next((s for s in state["sections"] if s["id"] == sec["parent_id"]), None)
                if parent and parent not in expanded:
                    expanded.insert(0, parent)
        
        # Assemble with explicit section markers
        context_parts = []
        for sec in expanded[:8]:
            context_parts.append(f"### Section: {sec['title']} (ID: {sec['id']})\n{sec['content'][:1200]}")
        
        assembled = "\n\n".join(context_parts)
        state["assembled_context"] = assembled
        state["context_tokens_used"] = int(len(assembled.split()) * 1.3)
        state["citations"] = [{"section": s["title"], "id": s["id"]} for s in expanded[:8]]
        
        return state

7. Final Answer Generator

class AnswerGeneratorAgent:
    """Generates final answer from assembled context"""
    
    def __init__(self):
        self.llm = ChatOpenAI(model="gpt-4", temperature=0.1)
    
    def generate(self, state: LongContextState) -> LongContextState:
        if state["final_answer"]:
            return state  # Already generated (e.g., hierarchical)
        
        strategy = state["selected_strategy"]
        
        if strategy == "native_long_context":
            prompt = f"""Answer this question using the full document.

Document:
{state['document_text'][:8000]}

Question: {state['query']}

Cite specific sections:"""
        else:
            prompt = f"""Answer this question using the provided context.

Strategy used: {strategy}
Context:
{state['assembled_context']}

Question: {state['query']}

Provide a comprehensive answer with section citations:"""
        
        resp = self.llm.invoke(prompt)
        state["final_answer"] = resp.content
        return state

8. LangGraph Workflow with Conditional Routing

class LongContextMemory:
    def __init__(self, redis_client: redis.Redis):
        self.redis = redis_client
    
    def save_execution(self, state: LongContextState):
        record = {
            "document_id": state["document_id"],
            "query_type": state["query_type"],
            "strategy": state["selected_strategy"],
            "tokens_used": state["context_tokens_used"],
            "timestamp": datetime.now().isoformat()
        }
        key = f"long_context_executions:{state['document_id']}"
        history = json.loads(self.redis.get(key) or "[]")
        history.append(record)
        self.redis.set(key, json.dumps(history[-50:]))

def build_long_context_graph():
    workflow = StateGraph(LongContextState)
    
    profiler = DocumentProfilerAgent()
    router = StrategyRouterAgent(redis.Redis())
    summarizer = HierarchicalSummarizerAgent()
    parent_child = ParentChildRetrieverAgent()
    cross_section = CrossSectionAssemblerAgent()
    generator = AnswerGeneratorAgent()
    
    workflow.add_node("profile", profiler.profile)
    workflow.add_node("route", router.route)
    workflow.add_node("hierarchical_summary", summarizer.summarize)
    workflow.add_node("parent_child_retrieve", parent_child.retrieve)
    workflow.add_node("cross_section_assemble", cross_section.assemble)
    workflow.add_node("native_context", lambda s: s)  # Pass-through
    workflow.add_node("section_aware", parent_child.retrieve)  # Reuses parent-child with structure
    workflow.add_node("generate", generator.generate)
    
    workflow.set_entry_point("profile")
    workflow.add_edge("profile", "route")
    
    def route_to_strategy(state: LongContextState) -> str:
        strategy = state["selected_strategy"]
        return {
            "hierarchical_summary": "hierarchical_summary",
            "parent_child": "parent_child_retrieve",
            "cross_section_assembly": "cross_section_assemble",
            "native_long_context": "native_context",
            "section_aware": "section_aware"
        }[strategy]
    
    workflow.add_conditional_edges(
        "route",
        route_to_strategy,
        {
            "hierarchical_summary": "hierarchical_summary",
            "parent_child_retrieve": "generate",
            "cross_section_assemble": "generate",
            "native_context": "generate",
            "section_aware": "generate"
        }
    )
    
    workflow.add_edge("hierarchical_summary", END)
    workflow.add_edge("generate", END)
    
    return workflow.compile()

9. FastAPI Backend

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel

app = FastAPI(title="Long-Context Orchestrator API")
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])

graph = build_long_context_graph()
memory = LongContextMemory(redis.Redis())

class LongContextRequest(BaseModel):
    document_id: str
    document_text: str
    query: str
    conversation_id: str = "default"

@app.post("/analyze")
async def analyze_long_document(req: LongContextRequest):
    initial_state = LongContextState(
        messages=[HumanMessage(content=req.query)],
        conversation_id=req.conversation_id,
        document_id=req.document_id,
        document_text=req.document_text,
        token_count=0,
        page_count=0,
        section_count=0,
        hierarchy_depth=0,
        has_structure=False,
        sections=[],
        query=req.query,
        query_type="factual",
        relevant_section_ids=[],
        selected_strategy="parent_child",
        strategy_reasoning="",
        assembled_context="",
        final_answer="",
        citations=[],
        context_tokens_used=0,
        estimated_cost_usd=0.0,
        latency_ms=0.0,
        past_strategies=[],
        document_type_history={}
    )
    
    result = graph.invoke(initial_state)
    memory.save_execution(result)
    
    # Estimate cost ($0.01 per 1K tokens for GPT-4)
    result["estimated_cost_usd"] = round(result["context_tokens_used"] / 1000 * 0.01, 4)
    
    return {
        "document_profile": {
            "token_count": result["token_count"],
            "page_count": result["page_count"],
            "section_count": result["section_count"],
            "hierarchy_depth": result["hierarchy_depth"],
            "has_structure": result["has_structure"]
        },
        "query_analysis": {
            "query_type": result["query_type"],
            "relevant_sections": result["relevant_section_ids"]
        },
        "strategy": {
            "selected": result["selected_strategy"],
            "reasoning": result["strategy_reasoning"]
        },
        "answer": result["final_answer"],
        "citations": result["citations"],
        "metrics": {
            "context_tokens_used": result["context_tokens_used"],
            "estimated_cost_usd": result["estimated_cost_usd"]
        }
    }

10. Frontend: Long-Context Analysis Dashboard

// components/LongContextDashboard.tsx
import React, { useState } from 'react';

export const LongContextDashboard: React.FC = () => {
  const [docText, setDocText] = useState(`# Chapter 1: Definitions
"Agreement" means this Master Services Agreement between parties.
"Confidential Information" means all non-public data...

# Chapter 2: Scope of Services
Provider shall deliver the Services as described in Exhibit A.
Any changes require written amendment signed by both parties.
Services include consulting, implementation, and support.

# Chapter 3: Payment Terms
Client shall pay invoices within 30 days of receipt.
Late payments accrue interest at 1.5% per month.
Fees are structured as milestone-based payments.

# Chapter 4: Intellectual Property
All work product created under this Agreement belongs to Client.
Provider retains rights to pre-existing IP and general methodologies.
License grants Client perpetual, worldwide, royalty-free use.

# Chapter 5: Indemnification
Provider shall indemnify Client against third-party claims.
Liability cap: $15M aggregate for all claims.
Exclusions: willful misconduct and gross negligence.

# Chapter 6: Term and Termination
Initial term: 24 months from Effective Date.
Either party may terminate for material breach with 30-day cure.
Termination for convenience requires 90-day written notice.`);
  const [query, setQuery] = useState('How do the payment terms relate to the termination provisions?');
  const [result, setResult] = useState<any>(null);
  const [loading, setLoading] = useState(false);

  const handleAnalyze = async () => {
    setLoading(true);
    try {
      const response = await fetch('http://localhost:8000/analyze', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          document_id: 'contract-msa-001',
          document_text: docText,
          query,
          conversation_id: 'legal-demo'
        })
      });
      setResult(await response.json());
    } finally {
      setLoading(false);
    }
  };

  const strategyColors: Record<string, string> = {
    hierarchical_summary: 'from-purple-600 to-pink-600',
    parent_child: 'from-blue-600 to-cyan-600',
    cross_section_assembly: 'from-orange-600 to-red-600',
    native_long_context: 'from-green-600 to-emerald-600',
    section_aware: 'from-indigo-600 to-violet-600'
  };

  return (
    <div className="p-6 max-w-7xl mx-auto bg-gray-50 min-h-screen">
      <h1 className="text-3xl font-bold mb-2">📚 Long-Context Document Orchestrator</h1>
      <p className="text-gray-600 mb-6">Adaptive strategy selection for long-document RAG</p>

      <div className="grid grid-cols-2 gap-6 mb-6">
        <div className="bg-white p-4 rounded-lg shadow">
          <h2 className="font-bold mb-3">Document</h2>
          <textarea
            className="w-full p-2 border rounded font-mono text-xs"
            rows={15}
            value={docText}
            onChange={e => setDocText(e.target.value)}
          />
        </div>
        <div className="bg-white p-4 rounded-lg shadow">
          <h2 className="font-bold mb-3">Query</h2>
          <textarea
            className="w-full p-3 border rounded"
            rows={3}
            value={query}
            onChange={e => setQuery(e.target.value)}
          />
          <div className="mt-3 space-y-2">
            <button onClick={() => setQuery('What is the liability cap in Section 5?')}
              className="block text-xs bg-gray-100 hover:bg-gray-200 px-3 py-1 rounded w-full text-left">
              Factual: "What is the liability cap in Section 5?"
            </button>
            <button onClick={() => setQuery('Summarize the key obligations of both parties')}
              className="block text-xs bg-gray-100 hover:bg-gray-200 px-3 py-1 rounded w-full text-left">
              Thematic: "Summarize key obligations of both parties"
            </button>
            <button onClick={() => setQuery(query)}
              className="block text-xs bg-gray-100 hover:bg-gray-200 px-3 py-1 rounded w-full text-left">
              Cross-reference: "How do payment terms relate to termination?"
            </button>
          </div>
          <button onClick={handleAnalyze} disabled={loading}
            className="mt-4 w-full bg-blue-600 text-white px-6 py-2 rounded disabled:opacity-50">
            {loading ? 'Analyzing...' : 'Analyze with Adaptive Strategy'}
          </button>
        </div>
      </div>

      {result && (
        <div className="space-y-4">
          <div className="grid grid-cols-3 gap-4">
            <div className="bg-white p-4 rounded-lg shadow">
              <h3 className="font-bold mb-3">📊 Document Profile</h3>
              <div className="space-y-2 text-sm">
                <div className="flex justify-between">
                  <span>Tokens:</span>
                  <span className="font-mono">{result.document_profile.token_count.toLocaleString()}</span>
                </div>
                <div className="flex justify-between">
                  <span>Pages (est):</span>
                  <span className="font-mono">{result.document_profile.page_count}</span>
                </div>
                <div className="flex justify-between">
                  <span>Sections:</span>
                  <span className="font-mono">{result.document_profile.section_count}</span>
                </div>
                <div className="flex justify-between">
                  <span>Hierarchy depth:</span>
                  <span className="font-mono">{result.document_profile.hierarchy_depth}</span>
                </div>
                <div className="flex justify-between">
                  <span>Has structure:</span>
                  <span>{result.document_profile.has_structure ? '✅' : '❌'}</span>
                </div>
              </div>
            </div>

            <div className={`bg-gradient-to-br ${strategyColors[result.strategy.selected] || 'from-gray-600 to-gray-800'} text-white p-4 rounded-lg shadow`}>
              <h3 className="font-bold mb-2 text-sm opacity-90">SELECTED STRATEGY</h3>
              <div className="text-xl font-bold">
                {result.strategy.selected.replace(/_/g, ' ').toUpperCase()}
              </div>
              <p className="text-xs mt-2 opacity-90">{result.strategy.reasoning}</p>
            </div>

            <div className="bg-white p-4 rounded-lg shadow">
              <h3 className="font-bold mb-3">🎯 Query Analysis</h3>
              <div className="space-y-2 text-sm">
                <div>
                  <span className="text-gray-600">Type:</span>
                  <span className="ml-2 font-semibold capitalize">{result.query_analysis.query_type}</span>
                </div>
                <div>
                  <span className="text-gray-600">Relevant sections:</span>
                  <div className="flex flex-wrap gap-1 mt-1">
                    {result.query_analysis.relevant_sections.map((id: string, i: number) => (
                      <span key={i} className="bg-blue-100 text-blue-700 text-xs px-2 py-0.5 rounded font-mono">
                        {id}
                      </span>
                    ))}
                  </div>
                </div>
                <hr className="my-2" />
                <div className="flex justify-between">
                  <span>Context tokens:</span>
                  <span className="font-mono">{result.metrics.context_tokens_used}</span>
                </div>
                <div className="flex justify-between">
                  <span>Est. cost:</span>
                  <span className="font-mono">${result.metrics.estimated_cost_usd}</span>
                </div>
              </div>
            </div>
          </div>

          <div className="bg-white p-6 rounded-lg shadow">
            <h3 className="font-bold mb-3">💬 Answer</h3>
            <div className="prose max-w-none whitespace-pre-wrap text-gray-800">
              {result.answer}
            </div>
            {result.citations.length > 0 && (
              <div className="mt-4 pt-4 border-t">
                <h4 className="text-sm font-bold mb-2">📎 Citations</h4>
                <div className="flex flex-wrap gap-2">
                  {result.citations.map((c: any, i: number) => (
                    <span key={i} className="bg-gray-100 text-gray-700 text-xs px-2 py-1 rounded">
                      [{c.id}] {c.section}
                    </span>
                  ))}
                </div>
              </div>
            )}
          </div>
        </div>
      )}
    </div>
  );
};

Real-Time Use Case: M&A Contract Intelligence Platform

A global law firm processes 200-page M&A agreements. An associate asks three different queries on the same contract:

Query 1: "What is the liability cap in Section 5?"

  • Profiler: Detects 6 chapters, hierarchy depth 1, clear structure

  • Router: Classifies as factual → selects parent_child strategy

  • Execution: Retrieves paragraph mentioning "liability cap," expands to full Chapter 5 context

  • Result: Precise answer with citation to Section 5, cost: $0.008

Query 2: "Summarize the key obligations of both parties across the agreement"

  • Profiler: Same document profile

  • Router: Classifies as thematic → selects hierarchical_summary

  • Execution: Summarizes each chapter, then synthesizes global answer

  • Result: Comprehensive thematic answer covering all 6 chapters, cost: $0.025

Query 3: "How do the payment terms relate to the termination provisions?"

  • Profiler: Identifies cross-section query

  • Router: Classifies as cross_reference, identifies Sections 3 and 6 as relevant → selects cross_section_assembly

  • Execution: Retrieves Chapter 3 (Payment) and Chapter 6 (Termination) with their parent context

  • Result: Answer explaining how unpaid invoices affect termination rights, cost: $0.015

Memory-driven adaptation: After 50 queries on similar M&A contracts, the system learns that factual queries on structured contracts consistently succeed with parent_child, while cross_reference queries benefit from cross_section_assembly. It locks in these patterns, reducing routing latency by 40% on subsequent queries.

Conclusion

Long-context documents cannot be handled with a one-size-fits-all approach. The naive choices fixed chunking, huge chunks, or stuffing everything into a long-context window each fail in predictable ways. The enterprise answer is adaptive strategy selection: profile the document, classify the query, and route to the optimal strategy (hierarchical summarization for themes, parent-child for facts, cross-section assembly for references, native long-context for short docs). By implementing this as a multi-agent LangGraph system with persistent memory, organizations transform long-document RAG from a fragile prototype into a reliable enterprise capability. Each query gets the right treatment, costs are controlled by using only the context needed, and the system continuously learns which strategies work best for which document-query combinations. In the enterprise, where contracts, manuals, and regulations routinely span hundreds of pages, this adaptive approach isn't optional it's the difference between a system that works on demos and one that works in production.