HunterAlphaHub
OpenRouter model reference Facts from the public catalogue, dated and labelled

Tutorial

Building a Long Document Analyzer with mimo-v2: A Step-by-Step Guide

Learn how to build a production-ready long document analysis tool using Xiaomi mimo-v2's 1M context window. Complete with code examples and best practices.

Hunter Alpha Hub Team 26 March 2026 12 min read

Building a Long Document Analyzer with mimo-v2: A Step-by-Step Guide

Introduction

This guide walks through building a complete document analysis tool using Xiaomi mimo-v2’s 1M token context window. We’ll build a tool that can:

  • Process PDF documents up to 500+ pages
  • Extract structured information
  • Answer questions about document content
  • Generate summaries at multiple granularity levels

By the end, you’ll have a working tool you can adapt for your own use cases.

Prerequisites

  • Basic Python knowledge
  • An OpenRouter API key (free at openrouter.ai)
  • Python 3.8+

Architecture Overview

Our tool has three main components:

  1. Document Loader — Converts PDFs to text
  2. Analysis Engine — Uses mimo-v2 to process and analyze
  3. Query Interface — Allows asking questions about the document
┌─────────────┐     ┌──────────────┐     ┌─────────────┐
│   PDF Doc   │ →   │  Document    │ →   │   Query     │
│             │     │   Analyzer   │     │   Results   │
└─────────────┘     └──────────────┘     └─────────────┘

Step 1: Setting Up the Environment

# Create virtual environment
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate

# Install dependencies
pip install openai pypdf2 rich

Create a .env file with your API key:

OPENROUTER_API_KEY=your_api_key_here

Step 2: Document Loading

We’ll use PyPDF2 to extract text from PDFs:

from PyPDF2 import PdfReader
from pathlib import Path

def load_pdf(pdf_path: str) -> str:
    """Extract text from a PDF file."""
    reader = PdfReader(pdf_path)
    text = ""
    for page in reader.pages:
        text += page.extract_text()
    return text

# Usage
document_text = load_pdf("technical-manual.pdf")
print(f"Loaded {len(document_text)} characters")

Step 3: Building the Analysis Engine

Now we’ll create the core analysis class:

from openai import OpenAI
import os
from dotenv import load_dotenv

load_dotenv()

class DocumentAnalyzer:
    def __init__(self, document_text: str):
        self.document = document_text
        self.client = OpenAI(
            api_key=os.getenv("OPENROUTER_API_KEY"),
            base_url="https://openrouter.ai/api/v1"
        )
        self.model = "xiaomi/mimo-v2"

    def analyze(self, prompt: str) -> str:
        """Send a prompt about the document to mimo-v2."""
        full_prompt = f"""Here is a document:

{self.document}

---

{prompt}"""

        response = self.client.chat.completions.create(
            model=self.model,
            messages=[
                {"role": "system", "content": "You are a document analysis assistant. Answer questions based only on the provided document."},
                {"role": "user", "content": full_prompt}
            ],
            max_tokens=4096
        )
        return response.choices[0].message.content

Step 4: Adding Analysis Methods

Let’s add specific analysis capabilities:

class DocumentAnalyzer:
    # ... (previous code)

    def get_summary(self, length: str = "medium") -> str:
        """Get a summary of the document."""
        length_instructions = {
            "short": "Summarize in 2-3 sentences.",
            "medium": "Summarize in one paragraph with 5-7 bullet points.",
            "long": "Provide a detailed summary with executive overview, key findings, and conclusions."
        }
        return self.analyze(f"{length_instructions[length]}")

    def extract_entities(self, entity_type: str = "all") -> str:
        """Extract specific entities from the document."""
        return self.analyze(f"Extract all {entity_type} mentioned in this document. List them in a structured format.")

    def find_section(self, topic: str) -> str:
        """Find and extract content related to a specific topic."""
        return self.analyze(f"Find all sections discussing '{topic}'. Quote the relevant passages.")

    def compare_sections(self, section1: str, section2: str) -> str:
        """Compare two topics within the document."""
        return self.analyze(f"Compare how the document discusses '{section1}' vs '{section2}'. What are the similarities and differences?")

    def fact_check(self, claim: str) -> str:
        """Verify if a claim is supported by the document."""
        return self.analyze(f"Is the following claim supported by this document? '{claim}' Provide evidence from the text.")

Step 5: Building the Query Interface

Create a simple command-line interface:

from rich.console import Console
from rich.markdown import Markdown

def interactive_mode(analyzer: DocumentAnalyzer):
    """Run an interactive query session."""
    console = Console()
    console.print("[bold green]Document Analyzer Ready![/bold green]")
    console.print("Type 'quit' to exit, 'summary' for summary, 'help' for commands.")

    while True:
        try:
            query = console.input("\n[bold blue]You:[/bold blue] ")

            if query.lower() == 'quit':
                break
            elif query.lower() == 'summary':
                result = analyzer.get_summary()
            elif query.lower() == 'help':
                console.print("""
[bold]Available commands:[/bold]
  summary [short|medium|long] - Get document summary
  entities [type] - Extract entities
  find <topic> - Find sections about a topic
  compare <topic1> <topic2> - Compare two topics
  fact <claim> - Fact check a claim
                """)
                continue
            else:
                result = analyzer.analyze(query)

            console.print("\n[bold green]Assistant:[/bold green]")
            console.print(Markdown(result))

        except Exception as e:
            console.print(f"[red]Error: {e}[/red]")

# Main execution
if __name__ == "__main__":
    import sys

    if len(sys.argv) < 2:
        print("Usage: python analyzer.py <pdf_file>")
        sys.exit(1)

    pdf_path = sys.argv[1]
    document_text = load_pdf(pdf_path)
    analyzer = DocumentAnalyzer(document_text)
    interactive_mode(analyzer)

Step 6: Handling Ultra-Long Documents

For documents approaching the 1M token limit:

def chunk_document(text: str, chunk_size: int = 400000, overlap: int = 10000) -> list:
    """Split a document into overlapping chunks."""
    chunks = []
    start = 0
    while start < len(text):
        end = start + chunk_size
        # Try to break at paragraph boundary
        if end < len(text):
            break_point = text.rfind('\n\n', start, end)
            if break_point > start:
                end = break_point
        chunks.append(text[start:end])
        start = end - overlap
    return chunks

class ChunkedAnalyzer:
    def __init__(self, document_text: str):
        self.chunks = chunk_document(document_text)
        self.client = OpenAI(
            api_key=os.getenv("OPENROUTER_API_KEY"),
            base_url="https://openrouter.ai/api/v1"
        )

    def analyze_all(self, prompt: str) -> list:
        """Analyze all chunks and return individual results."""
        results = []
        for i, chunk in enumerate(self.chunks):
            response = self.client.chat.completions.create(
                model="xiaomi/mimo-v2",
                messages=[
                    {"role": "system", "content": "Answer based only on the provided text."},
                    {"role": "user", "content": f"Document chunk {i+1}:\n\n{chunk}\n\n---\n\n{prompt}"}
                ]
            )
            results.append({
                "chunk": i + 1,
                "content": response.choices[0].message.content
            })
        return results

    def synthesize(self, results: list) -> str:
        """Combine results from all chunks into a final answer."""
        combined = "\n\n".join([r["content"] for r in results])
        response = self.client.chat.completions.create(
            model="xiaomi/mimo-v2",
            messages=[
                {"role": "system", "content": "Synthesize the following analysis results into a coherent answer."},
                {"role": "user", "content": combined}
            ]
        )
        return response.choices[0].message.content

Step 7: Production Considerations

Error Handling

import time
from openai import RateLimitError

def analyze_with_retry(self, prompt: str, max_retries: int = 3):
    """Analyze with exponential backoff retry."""
    for attempt in range(max_retries):
        try:
            return self.analyze(prompt)
        except RateLimitError:
            if attempt == max_retries - 1:
                raise
            wait_time = 2 ** attempt
            time.sleep(wait_time)

Caching Results

import hashlib
import json
from pathlib import Path

class CachedAnalyzer(DocumentAnalyzer):
    def __init__(self, document_text: str, cache_dir: str = ".cache"):
        super().__init__(document_text)
        self.cache_dir = Path(cache_dir)
        self.cache_dir.mkdir(exist_ok=True)
        self.doc_hash = hashlib.md5(document_text.encode()).hexdigest()

    def analyze(self, prompt: str) -> str:
        prompt_hash = hashlib.md5(prompt.encode()).hexdigest()
        cache_file = self.cache_dir / f"{self.doc_hash}_{prompt_hash}.json"

        if cache_file.exists():
            with open(cache_file) as f:
                return json.load(f)["response"]

        response = super().analyze(prompt)

        with open(cache_file, "w") as f:
            json.dump({"prompt": prompt, "response": response}, f)

        return response

Complete Working Example

Here’s a complete script ready to run:

#!/usr/bin/env python3
"""
Document Analyzer using Xiaomi mimo-v2
Usage: python analyzer.py <pdf_file>
"""

from PyPDF2 import PdfReader
from openai import OpenAI
from rich.console import Console
from rich.markdown import Markdown
import os
from dotenv import load_dotenv

load_dotenv()

def load_pdf(pdf_path):
    reader = PdfReader(pdf_path)
    return "".join(page.extract_text() for page in reader.pages)

def analyze_document(document_text, query):
    client = OpenAI(
        api_key=os.getenv("OPENROUTER_API_KEY"),
        base_url="https://openrouter.ai/api/v1"
    )

    response = client.chat.completions.create(
        model="xiaomi/mimo-v2",
        messages=[
            {"role": "system", "content": "You are a helpful document assistant."},
            {"role": "user", "content": f"Document:\n{document_text}\n\nQuestion: {query}"}
        ],
        max_tokens=4096
    )
    return response.choices[0].message.content

if __name__ == "__main__":
    import sys
    console = Console()

    if len(sys.argv) < 2:
        console.print("[red]Usage: python analyzer.py <pdf_file>[/red]")
        sys.exit(1)

    console.print("[bold]Loading document...[/bold]")
    doc_text = load_pdf(sys.argv[1])
    console.print(f"[green]Loaded {len(doc_text)} characters[/green]")

    while True:
        query = console.input("\n[bold]Query:[/bold] ")
        if query == 'quit':
            break

        console.print("[bold]Analyzing...[/bold]")
        result = analyze_document(doc_text, query)
        console.print(Markdown(result))

Best Practices

Do’s

  • Be specific in prompts — “Extract all API endpoints” vs “What’s in this doc?”
  • Use system messages to set context for long sessions
  • Verify critical information — LLMs can occasionally misquote
  • Cache expensive results to save API calls

Don’ts

  • Don’t expect perfect recall beyond 500K tokens
  • Don’t skip error handling for production use
  • Don’t use for time-sensitive tasks — expect 20-60s responses

Conclusion

You now have a working document analyzer that can process hundreds of pages in a single query. The code is modular — swap in different PDF loaders, add output formats, or integrate into existing workflows.

The key advantage of mimo-v2 is handling documents that would overflow other models’ context windows. For legal docs, technical manuals, or research papers, this tool can save hours of manual work.

Further Reading


Have you built something cool with mimo-v2? Its current page, with pricing, is Xiaomi MiMo-V2.5.

mimo-v2Document AnalysisPythonTutorial1M ContextCode Example

Keep reading

Send us a link

A project built with Jev, a post, a video, a correction, a tip. We open the link, check it says what you said it says, and write the entry ourselves.

Required a link, and an email to reply to. Optional everything else.

Add context — all optional

One or two sentences about what it does, in your words. We write the entry ourselves.

Cost, latency, a benchmark — anything you measured. We attribute these to you.

We store what you type and email it to ourselves. No IP address, no user agent, no referrer — the same rule as the mailing list.

What happens to what you send →