Doc Analyzer: Privacy-First Document Analysis with DeepSeek R1

Document Analysis with RAG and DeepSeek R1
Secure and private document analysis with local AI
Matteo 5 min

In the 2025 business landscape, AI-powered document analysis has become a necessity. However, when it comes to sensitive documents, privacy remains a primary concern. How can we harness the power of AI while keeping data safe within our own systems?

Today I’m introducing Doc Analyzer, a project born precisely to address this need. An application that combines the power of the DeepSeek R1 model with the RAG (Retrieval Augmented Generation) architecture to analyze documents entirely locally, without ever sending sensitive data to external clouds.

The Architecture: Privacy by Design

Doc Analyzer is built on the “privacy by design” principle. Every component was designed with data security as a primary requirement, not as an add-on feature. The application uses FastAPI as the backend, Gradio for the user interface, and a sophisticated document-processing pipeline based on LangChain.

The architecture is made up of three main components that work together to ensure secure and efficient document analysis.

PDFProcessor: The Smart Extractor

The PDFProcessor is the first step in our pipeline. This class, implemented using PyMuPDF, takes care of:

  1. Handling PDF document uploads securely, supporting both local files and uploads via Gradio
  2. Extracting text while preserving the document’s formatting and structure
  3. Implementing a chunking strategy that preserves semantic context

The most interesting part of the PDFProcessor is how it handles temporary files:

def process(self, file_obj):
    with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as tmp_file:
        if hasattr(file_obj, 'name'):
            file_path = file_obj.name
        else:
            content = file_obj.read()
            tmp_file.write(content)
            file_path = tmp_file.name

    try:
        loader = PyMuPDFLoader(file_path)
        documents = loader.load()
        chunks = self.text_splitter.split_documents(documents)
        return chunks
    finally:
        if 'tmp_file' in locals():
            os.unlink(file_path)

This code ensures that files are deleted after processing, leaving no sensitive traces on disk.

RAGProcessor: The Heart of the System

The RAGProcessor is the most sophisticated component of the application. It implements the RAG (Retrieval Augmented Generation) architecture, optimized for local use.

Unlike traditional language models that can generate “hallucinations” based on their general knowledge, the RAGProcessor anchors its answers directly to the content of the provided documents. This “grounded” approach ensures that answers are always based on information actually present in the documents, drastically reducing the risk of hallucinations (you can dig deeper here).

Let’s look at how it works in detail:

Initialization and Configuration

def __init__(self):
    self.model_name = os.getenv('DEEPSEEK_MODEL', 'deepseek-r1:14b')
    self.chroma_path = os.getenv('CHROMA_DB_PATH', './data/chroma')
    self.persist_db = os.getenv('PERSIST_VECTORDB', 'false').lower() == 'true'

    self.embeddings = OllamaEmbeddings(
        model=self.model_name,
        base_url=f"http://{os.getenv('OLLAMA_HOST', 'localhost')}:{os.getenv('OLLAMA_PORT', '11434')}"
    )

Initialization configures the DeepSeek model and the ChromaDB vector database. The class supports both persistence (keeping data between queries) and an ephemeral database mode (deleting data after each query), enabling different privacy strategies.

Vector Database Management

def query(self, question, chunks):
    if not chunks:
        raise ValueError("No document chunks provided")
        
    if self.vectordb is None:
        self.vectordb = Chroma(
            persist_directory=self.chroma_path,
            embedding_function=self.embeddings
        )

    if not self.persist_db:
        collections = self.vectordb._client.list_collections()
        for collection in collections:
            self.vectordb._client.delete_collection(collection.name)

This part is crucial for privacy: if persistence isn’t enabled, the system automatically deletes previous collections, ensuring that no data stays stored between sessions.

The Retrieval Process

The heart of RAG is the retrieval process, where the system fetches the chunks most relevant to the query:

self.vectordb = Chroma.from_documents(
    documents=chunks,
    embedding=self.embeddings,
    persist_directory=self.chroma_path
)

retriever = self.vectordb.as_retriever()
relevant_chunks = retriever.get_relevant_documents(question)

Generating the Answer

Answer generation is optimized for the document context:

context = "\n\n".join([chunk.page_content for chunk in relevant_chunks])

prompt = f"""Based on the following document excerpts, answer the question.
First, identify which parts of the document are relevant to the question.
Then, provide a detailed answer using ONLY the information from these parts.
If the information needed isn't in the excerpts, clearly state this.

Document excerpts: {context}

Question: {question}

Detailed answer:"""

response = client.chat(
    model=self.model_name,
    messages=[{
        "role": "user",
        "content": prompt
    }]
)

The prompt is structured to maximize precision and minimize hallucinations, a common problem in RAG systems.

User Interface with Gradio

The user interface is implemented with Gradio and integrated into FastAPI:

interface = gr.Interface(
    fn=process_and_query,
    inputs=[
        gr.File(label="Upload a PDF", file_types=[".pdf"]),
        gr.Textbox(label="Ask a question about the document")
    ],
    outputs=gr.Textbox(label="Answer"),
    title="DocAnalyzer",
    description="Analyze PDF documents with DeepSeek R1",
)

Deployment and Configuration

The system is containerized with Docker, using an optimized slim image as its base:

FROM python:3.11-slim

RUN apt-get update && apt-get install -y \
    poppler-utils \
    tesseract-ocr \
    libtesseract-dev \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app

RUN mkdir -p /app/data/chroma && chmod 777 /app/data/chroma

COPY requirements.txt requirements-dev.txt setup.py ./
COPY src/ ./src/
COPY tests/ ./tests/

RUN pip install --no-cache-dir -r requirements.txt \
    && pip install --no-cache-dir -r requirements-dev.txt \
    && pip install -e .

ENV PYTHONPATH=/app/src

EXPOSE 8000

CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]

Use Cases and Performance

Doc Analyzer excels in several scenarios:

  1. Legal Document Analysis
  • Extracting specific clauses
  • Identifying potential risks
  • Comparing contract versions
  1. Technical Analysis
  • Reviewing technical specifications
  • Extracting requirements
  • Validating documentation
  1. Compliance and Audit
  • Checking regulatory compliance
  • Identifying policy violations
  • Documenting procedures

Performance is very good: on a MacBook Pro M3 Pro with 18GB of RAM (4 of which are dedicated to Docker), the system processes 100-page documents in under 30 seconds, with query response times under 6 seconds.

Conclusion: The Future of Document Analysis

Doc Analyzer shows that it’s possible to build advanced document analysis systems while keeping full control over your data. The RAG architecture combined with DeepSeek R1 offers a concrete alternative to cloud services, ideal for organizations with strict privacy requirements.

The project is open source and constantly evolving. Planned developments include:

  • Support for other document types
  • Integration with versioning systems
  • User interface improvements
  • Performance optimizations

P.S. If you’re trying to install DeepSeek R1 locally, take a look at my dedicated guide: Installing DeepSeek Locally with Ollama.


Useful Resources

Copiato