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.
| Capability | Use Case | Best Model |
|---|---|---|
| Text extraction (OCR) | Convert scanned docs to text | Qwen-VL |
| Structured extraction | Pull fields from forms/invoices | DeepSeek-V4 |
| Table parsing | Extract tables from PDFs | GLM-4 |
| Document summarization | TL;DR for long reports | DeepSeek-V4 |
| Document classification | Route docs to workflows | Kimi-K2 |
| Multi-page analysis | Cross-reference across pages | GLM-4 (long context) |
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Upload │───▶│ OCR │───▶│ Chunk │───▶│ LLM │───▶│ Output │
│ (PDF) │ │ (Qwen) │ │ │ │ Analysis │ │ (JSON) │
└──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘
│
▼
┌──────────┐
│ Vector │
│ Store │
└──────────┘
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)
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))
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'])}")
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")
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)
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)
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")
Processing 1,000 pages of documents:
Equivalent with GPT-4V + GPT-4o: ~$20-30 — 10-15× more expensive.
| Task | DeepSeek-V4 | GLM-4 | Qwen-Max | GPT-4o |
|---|---|---|---|---|
| Invoice extraction (F1) | 94.2% | 92.8% | 93.5% | 95.1% |
| Table extraction | 89.5% | 91.2% | 88.7% | 92.3% |
| Document classification | 96.1% | 95.4% | 94.8% | 97.2% |
| Summary quality (human eval) | 4.2/5 | 4.1/5 | 4.0/5 | 4.3/5 |
For related guides, see our RAG implementation guide and AI agent development articles.
Last updated: August 2026. Document AI capabilities improve rapidly.