Build a Long-Document Analyst with DeepSeek-V4.1-Flash's 1M-Token Context
Ask a colleague what a vendor contract says about termination and they skim, jump, and remember. Ask a retrieval pipeline and it hands you eight chunks mentioning "termination" and hopes. It never sees the document whole, so it cannot tell you clause 14 contradicts the summary on page 3. This tutorial closes that gap: a Python tool that loads a long PDF into one labelled context block and asks DeepSeek-V4.1-Flash for answers with checkable citations. DeepSeek announced the model on September 10, 2026, with a 1 million token context window.
Why Chunk and Retrieve Loses the Argument
Retrieval scores small pieces against a query. That is right when the answer sits in one paragraph, and wrong when the answer is about the document: which section contradicts which, whether a figure matches the table describing it, how a definition on page 4 changes a clause on page 120. Top-k search returns near-identical passages and misses the page that reframes everything.
Long context is a different bet: put everything in the prompt, let the model attend across all of it, and manage size, cost, and precision yourself. For a 300-page report, that trade pays off.
Setup: Your Key and the Right Model String
You need one thing before any code: an API key from DeepSeek. Export it so nothing hardcodes a secret:
export DEEPSEEK_API_KEY="sk-your-key-here"The API is OpenAI-compatible. The base URL is https://api.deepseek.com, the endpoint is POST /chat/completions, and the model string is deepseek-flash — use it verbatim; the retired deepseek-v4-flash and deepseek-v4-flash-vision-exp names now temporarily route to V4.1-Flash anyway. Here is a client small enough to read in one breath:
import os, requests
API_KEY = os.environ["DEEPSEEK_API_KEY"]
BASE = "https://api.deepseek.com"
def ask(messages, max_tokens=1200):
resp = requests.post(
BASE + "/chat/completions",
headers={"Authorization": "Bearer " + API_KEY},
json={"model": "deepseek-flash",
"messages": messages,
"max_tokens": max_tokens},
timeout=600,
)
resp.raise_for_status()
return resp.json()["choices"][0]["message"]["content"]That is the whole integration; the rest is feeding, prompting, and paying for it.
Loading Documents into One Labelled Context Block
Extraction first. pypdf pulls text from most PDFs; run pip install pypdf requests, then load every page with a marker recording its origin. The marker is the feature: it lets the model cite a page and lets you verify later.
import sys
from pypdf import PdfReader
def load_pdf(path):
reader = PdfReader(path)
blocks = []
for i, page in enumerate(reader.pages, start=1):
body = page.extract_text() or ""
blocks.append("[{} p.{}]\n{}".format(path, i, body))
return blocks
pages = load_pdf(sys.argv[1])
ctx = "\n\n".join(pages)
print("{} pages, ~{} tokens".format(len(pages), len(ctx) // 4))That last line is your budget check. Exact counts need a tokenizer, but English runs about four characters per token, so len(ctx) // 4 lands in the ballpark. A 300-page PDF usually falls between 150,000 and 400,000 tokens. The 1 million window is generous but not infinite, and transcripts plus a few annual reports get close to the edge. Print the estimate first.

Long Context First, or Map Then Reduce
Two strategies. Long context first sends the whole labelled block in one request. It wins when the answer needs the whole document: contradictions, summaries, anything where distant parts relate.
Map then reduce splits the text into page windows, asks each window the same question, then makes a final call over the partial answers. It wins when a document is too big to pay for at once, when you ask one question across hundreds of documents, or when you want to reuse cached window answers. The cost: the reduce step reasons only over what the map step kept, so a detail one window missed is gone. A decision rule that works:
def pick_strategy(est_tokens, needs_global):
if needs_global:
return "long_context"
if est_tokens < 800000:
return "long_context"
return "map_reduce"When in doubt, start with long context. Turning a global question into twenty local ones and hoping the reduce step reassembles the argument is how you get confident, wrong summaries.
Tables and Charts as Native Image Input
Text extraction mangles tables and drops charts. V4.1-Flash accepts images natively, so render the pages that matter and send them alongside the text. Keep one page per image: a whole-document screenshot is unreadable and wastes tokens on white space.
import base64
def image_part(path):
with open(path, "rb") as fh:
b64 = base64.b64encode(fh.read()).decode()
return {"type": "image_url",
"image_url": {"url": "data:image/png;base64," + b64}}
messages = [
{"role": "system", "content": "You read documents and cite page numbers."},
{"role": "user", "content": [
{"type": "text", "text": ctx + "\n\nExplain the chart on p.42."},
image_part("page_42.png"),
]},
]Mix text and images freely: text for prose and cheap navigation, images for pages where layout carries meaning. A model with native visual understanding reads the axis labels and the trend line.

Prompting for Citations You Can Actually Verify
Long context invites fluent invention: the model writes smoothly even when the document says nothing of the sort, because a plain question never tells it to stop. Demand markers, structure, and explicit permission to fail.
PROMPT = (
"Answer using only the context block. For every claim, cite the "
"marker in square brackets, e.g. [report.pdf p.12]. If the context "
"does not support an answer, reply exactly: not in the documents."
"\n\nReturn two sections:\nANSWER: ...\nSOURCES: marker - quoted line"
"\n\nCONTEXT:\n" + ctx
)Then run one verification pass — the step people skip and the one that catches the most errors:
verify = (
"For each SOURCES line, quote the exact sentence from the context "
"that supports it. If you cannot quote it, drop the claim."
)A citation the model cannot back with a quoted line is not a citation. It is a guess wearing a page number.
Make "not in the documents" a first-class answer, not a failure. A trustworthy analyst tells you when the source is silent; a tool that always answers is one you re-check by hand.
Caching, Batching, and Cost Control
Two habits keep the bill sane. Order the prompt so the reusable part comes first: static instructions, then the document block, then the question. If the document text is stable across calls, a cache hit covers the prefix and you pay full price only for the question.
- Keep instructions and the context block byte-identical between calls so the prefix stays cacheable.
- Put the changing question last, after everything stable.
- Batch non-urgent work and run it off-peak: off-peak rates are 50 percent of peak rates, so a nightly job costs half of the same job at midday.
- Set
max_tokensexplicitly. A ceiling stops a confused answer from rambling into a bill.
Cache economics improved too. DeepSeek notes cache-hit charges are often a large share of agent running costs, and V4.1-Flash's KV cache needs a quarter of the HBM and an eighth of the SSD storage of the previous generation — fewer bytes, cheaper hits.
Troubleshooting
- Answers drift on very long inputs. Attention degrades as context fills. Send the relevant section plus surrounding context, not the full 900,000 tokens.
- Answers cut off mid-sentence. You hit the token ceiling. Raise
max_tokensor ask for a shorter structure. - Rate limits. Back off, retry with a delay, and move bulk jobs off-peak.
- Still calling
deepseek-v4-pro? From 04:00 UTC on September 14, 2026 those requests route to V4.1-Flash at V4.1-Flash rates until V4.1-Pro launches. Switch todeepseek-flashand retest. - Another provider? The call is OpenAI-compatible, so swapping
BASEand the model string is usually the whole migration.
One note on hosting: a 552B-parameter mixture-of-experts model is a 2,000-GPU-plus class deployment, so self-hosting is not the path. The weights are on Hugging Face, but the API is the right default.
What to Build Next
Wrap the client in a CLI so you can point it at a file and a question without editing code. Keep a local index of past questions and answers keyed by document hash, so the second question about the same PDF is cheaper. Add a review queue for answers whose citations failed verification, cleared in batches rather than blocking every call.
The tradeoff to remember: 1M context buys simplicity, not automatic precision. You skip the embedding pipeline and the chunker, and take on prompt sizing, cache discipline, and citation checking. Build the verification pass from day one.



