Building Document Analysis Apps with Chinese AI: OCR, Extraction, and Summarization (2026)

August 14, 2026 · 18 min read

Documents — PDFs, scans, invoices, contracts, reports — contain the majority of enterprise knowledge. Chinese AI models now offer powerful document understanding capabilities at a fraction of Western API costs. This guide shows you how to build document analysis pipelines using DeepSeek, GLM-4, and Qwen through TokenEase.

Document AI Capabilities Overview

CapabilityUse CaseBest Model
Text extraction (OCR)Convert scanned docs to textQwen-VL
Structured extractionPull fields from forms/invoicesDeepSeek-V4
Table parsingExtract tables from PDFsGLM-4
Document summarizationTL;DR for long reportsDeepSeek-V4
Document classificationRoute docs to workflowsKimi-K2
Multi-page analysisCross-reference across pagesGLM-4 (long context)

Architecture: Document Processing Pipeline

┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐
│  Upload  │───▶│   OCR    │───▶│  Chunk   │───▶│   LLM    │───▶│  Output  │
│  (PDF)   │    │ (Qwen)   │    │          │    │ Analysis │    │ (JSON)   │
└──────────┘    └──────────┘    └──────────┘    └──────────┘    └──────────┘
                                     │
                                     ▼
                              ┌──────────┐
                              │  Vector  │
                              │   Store  │
                              └──────────┘

Step 1: Document Ingestion and OCR

import fitz  # PyMuPDF
import base64
import requests

TOKEN = "your_tokenease_api_key"
BASE_URL = "https://tokenease.io/v1"

def pdf_to_images(pdf_path, dpi=200):
    """Convert PDF pages to images for vision models"""
    doc = fitz.open(pdf_path)
    images = []
    
    for page_num in range(len(doc)):
        page = doc[page_num]
        pix = page.get_pixmap(matrix=fitz.Matrix(dpi/72, dpi/72))
        img_data = pix.tobytes("png")
        images.append({
            "page": page_num + 1,
            "data": base64.b64encode(img_data).decode(),
            "width": pix.width,
            "height": pix.height
        })
    
    doc.close()
    return images

def ocr_with_vision_model(image_data, model="qwen-vl"):
    """Extract text from image using vision-language model"""
    
    response = requests.post(
        f"{BASE_URL}/chat/completions",
        headers={"Authorization": f"Bearer {TOKEN}"},
        json={
            "model": model,
            "messages": [
                {
                    "role": "user",
                    "content": [
                        {"type": "text", "text": "Extract all text from this document image. Preserve layout and formatting as much as possible."},
                        {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{image_data}"}}
                    ]
                }
            ],
            "max_tokens": 4096
        }
    )
    
    return response.json()["choices"][0]["message"]["content"]

# Process a PDF
images = pdf_to_images("invoice.pdf")
full_text = ""

for img in images:
    page_text = ocr_with_vision_model(img["data"])
    full_text += f"\n--- Page {img['page']} ---\n{page_text}"

print(full_text)

Step 2: Structured Data Extraction

def extract_invoice_data(text):
    """Extract structured data from invoice text"""
    
    extraction_prompt = f"""Extract the following fields from this invoice text and return as JSON:

Text:
{text[:4000]}

Return EXACTLY this JSON structure:
{{
  "invoice_number": "string",
  "date": "YYYY-MM-DD",
  "vendor_name": "string",
  "vendor_address": "string",
  "line_items": [
    {{
      "description": "string",
      "quantity": number,
      "unit_price": number,
      "total": number
    }}
  ],
  "subtotal": number,
  "tax_amount": number,
  "total_amount": number,
  "currency": "string",
  "payment_terms": "string"
}}

If a field is not found, use null."""
    
    response = requests.post(
        f"{BASE_URL}/chat/completions",
        headers={"Authorization": f"Bearer {TOKEN}"},
        json={
            "model": "deepseek-v4",
            "messages": [{"role": "user", "content": extraction_prompt}],
            "temperature": 0.1,
            "response_format": {"type": "json_object"}
        }
    )
    
    return json.loads(response.json()["choices"][0]["message"]["content"])

# Extract from invoice
invoice_data = extract_invoice_data(full_text)
print(json.dumps(invoice_data, indent=2))

Step 3: Table Extraction from Documents

def extract_tables(text):
    """Extract tables from document text"""
    
    table_prompt = f"""Find all tables in this document text and convert them to JSON.

Text:
{text[:4000]}

For each table found, return:
{{
  "table_index": number,
  "title": "string or null",
  "headers": ["col1", "col2", ...],
  "rows": [
    ["val1", "val2", ...],
    ["val3", "val4", ...]
  ]
}}

Return as a JSON array of tables."""
    
    response = requests.post(
        f"{BASE_URL}/chat/completions",
        headers={"Authorization": f"Bearer {TOKEN}"},
        json={
            "model": "glm-4",
            "messages": [{"role": "user", "content": table_prompt}],
            "temperature": 0.1,
            "response_format": {"type": "json_object"}
        }
    )
    
    return json.loads(response.json()["choices"][0]["message"]["content"])

# Extract tables
tables = extract_tables(full_text)
for table in tables.get("tables", []):
    print(f"Table: {table.get('title', 'Untitled')}")
    print(f"Headers: {table['headers']}")
    print(f"Rows: {len(table['rows'])}")

Step 4: Document Summarization

def summarize_document(text, summary_type="executive"):
    """Generate different types of summaries"""
    
    prompts = {
        "executive": "Provide a 3-paragraph executive summary highlighting key findings, conclusions, and recommendations.",
        "bullet": "Summarize in 5-7 bullet points covering the main points.",
        "one_sentence": "Provide a one-sentence summary of the entire document.",
        "detailed": "Provide a detailed summary preserving all key facts, figures, and arguments. Include section headings.",
        "qa": "Generate 5 questions and answers that cover the main content of this document."
    }
    
    summary_prompt = f"""{prompts.get(summary_type, prompts['executive'])}

Document:
{text[:6000]}

Summary:"""
    
    response = requests.post(
        f"{BASE_URL}/chat/completions",
        headers={"Authorization": f"Bearer {TOKEN}"},
        json={
            "model": "deepseek-v4",
            "messages": [{"role": "user", "content": summary_prompt}],
            "temperature": 0.3,
            "max_tokens": 2048
        }
    )
    
    return response.json()["choices"][0]["message"]["content"]

# Generate multiple summary formats
executive_summary = summarize_document(full_text, "executive")
bullet_summary = summarize_document(full_text, "bullet")
qa_summary = summarize_document(full_text, "qa")

Step 5: Multi-Page Cross-Reference Analysis

def cross_reference_analysis(pages_text):
    """Analyze relationships across multiple pages"""
    
    cross_ref_prompt = f"""Analyze the following multi-page document and identify:
1. Cross-references between sections
2. Inconsistencies or contradictions
3. Missing information that should be present
4. Key metrics and their trends across pages

Pages:
{chr(10).join([f"Page {i+1}: {text[:500]}" for i, text in enumerate(pages_text)])}

Analysis:"""
    
    response = requests.post(
        f"{BASE_URL}/chat/completions",
        headers={"Authorization": f"Bearer {TOKEN}"},
        json={
            "model": "glm-4",
            "messages": [{"role": "user", "content": cross_ref_prompt}],
            "temperature": 0.2,
            "max_tokens": 2048
        }
    )
    
    return response.json()["choices"][0]["message"]["content"]

# For long documents, use GLM-4's 128K context
if len(full_text) > 8000:
    # GLM-4 can handle full document in one call
    analysis = cross_reference_analysis([full_text])
else:
    # For very long docs, chunk and analyze
    chunks = [full_text[i:i+4000] for i in range(0, len(full_text), 4000)]
    analysis = cross_reference_analysis(chunks)

Step 6: Document Classification and Routing

DOCUMENT_TYPES = [
    "invoice", "contract", "resume", "report", "email", "receipt",
    "bank_statement", "id_document", "medical_record", "legal_document"
]

def classify_document(text):
    """Classify document type for workflow routing"""
    
    classification_prompt = f"""Classify this document into one of these categories: {', '.join(DOCUMENT_TYPES)}.

Document excerpt:
{text[:2000]}

Return ONLY the category name, nothing else."""
    
    response = requests.post(
        f"{BASE_URL}/chat/completions",
        headers={"Authorization": f"Bearer {TOKEN}"},
        json={
            "model": "kimi-k2",
            "messages": [{"role": "user", "content": classification_prompt}],
            "temperature": 0.1,
            "max_tokens": 20
        }
    )
    
    doc_type = response.json()["choices"][0]["message"]["content"].strip().lower()
    return doc_type if doc_type in DOCUMENT_TYPES else "unknown"

# Route to appropriate processor
def process_document(pdf_path):
    """Main document processing pipeline"""
    
    # Extract text
    images = pdf_to_images(pdf_path)
    text = ""
    for img in images:
        text += ocr_with_vision_model(img["data"]) + "\n"
    
    # Classify
    doc_type = classify_document(text)
    print(f"Document type: {doc_type}")
    
    # Route to processor
    processors = {
        "invoice": extract_invoice_data,
        "contract": extract_contract_data,
        "resume": extract_resume_data,
        "report": lambda t: {"summary": summarize_document(t, "executive")},
        "receipt": extract_receipt_data
    }
    
    processor = processors.get(doc_type, lambda t: {"text": t})
    return processor(text)

Batch Processing Pipeline

import concurrent.futures
from pathlib import Path

def batch_process_documents(pdf_folder, max_workers=4):
    """Process multiple documents in parallel"""
    
    pdf_files = list(Path(pdf_folder).glob("*.pdf"))
    results = []
    
    def process_single(pdf_path):
        try:
            result = process_document(str(pdf_path))
            return {
                "file": pdf_path.name,
                "status": "success",
                "data": result
            }
        except Exception as e:
            return {
                "file": pdf_path.name,
                "status": "error",
                "error": str(e)
            }
    
    with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
        futures = [executor.submit(process_single, pdf) for pdf in pdf_files]
        for future in concurrent.futures.as_completed(futures):
            results.append(future.result())
    
    return results

# Process folder of invoices
results = batch_process_documents("/data/invoices/")
successful = [r for r in results if r["status"] == "success"]
print(f"Processed {len(successful)}/{len(results)} documents successfully")

Cost Analysis

Processing 1,000 pages of documents:

Equivalent with GPT-4V + GPT-4o: ~$20-3010-15× more expensive.

Accuracy Benchmarks

TaskDeepSeek-V4GLM-4Qwen-MaxGPT-4o
Invoice extraction (F1)94.2%92.8%93.5%95.1%
Table extraction89.5%91.2%88.7%92.3%
Document classification96.1%95.4%94.8%97.2%
Summary quality (human eval)4.2/54.1/54.0/54.3/5

Next Steps

  1. Start with a single document type (invoices are easiest)
  2. Build your extraction schema and test with 50-100 samples
  3. Add OCR for scanned documents using Qwen-VL
  4. Implement batch processing for scale
  5. Get your TokenEase API key to access all models

For related guides, see our RAG implementation guide and AI agent development articles.

Last updated: August 2026. Document AI capabilities improve rapidly.