typhoon-ocr2
Read Thai and English documents (PDFs and images) with Typhoon OCR. For every page you get:
- the page as Markdown (tables as HTML, formulas as LaTeX), and
- with Typhoon OCR V2, the layout elements in reading order: each one has a category (title, text, table, …), a bounding box and its text.
Links: Typhoon OCR V2 2B · Typhoon OCR V2 0.8B · Playground · Typhoon API docs
This package continues scb-10x/typhoon-ocr (pip install typhoon-ocr) with the Typhoon team's permission. It has a new name, so code that uses typhoon-ocr keeps working.
Install
pip install typhoon-ocr2
PDF support is included; you do not need to install Poppler.
Quick start: Typhoon API (no GPU needed)
import os
from typhoon_ocr2 import TyphoonOCR
os.environ["TYPHOON_API_KEY"] = "YOUR_KEY" # your Typhoon API key (or pass api_key="YOUR_KEY" below)
ocr = TyphoonOCR(
base_url="https://api.opentyphoon.ai/v1", # the Typhoon API
version="v2", # Typhoon OCR V2 ("v1.5" for Typhoon OCR 1.5)
upscale=True, # enlarge images under 1,600 px (recommended; see Settings)
)
pages = ocr.parse("report.pdf", pages="1-3") # a PDF or an image; pages: "1-3", 2, [1, 4] or None for all
for page in pages:
print(f"--- page {page.page}")
print(page.markdown)
Typhoon OCR V2 or 1.5
Switch with version; the rest of the code stays the same.
ocr_v15 = TyphoonOCR(
base_url="https://api.opentyphoon.ai/v1",
version="v1.5",
)
print(ocr_v15.parse("page.jpg")[0].markdown)
version="v2" |
version="v1.5" |
|
|---|---|---|
| model used on the Typhoon API | typhoon-ocr-v2 |
typhoon-ocr-v1.5 |
page.markdown |
yes | yes |
page.elements (category, box, text) |
yes | no (empty list) |
ask() / extract() |
yes | not tested |
version or model? version tells the package which prompt to send and how to read the answer. model is the model's name on the server. On the Typhoon API you only need version: the package picks the matching model for you. You need model when you run the model yourself (next section), because there the name depends on how you started the server.
Run the model yourself
Start a server, then point base_url and model at it:
# vLLM 0.33.0 or newer (GPU)
vllm serve typhoon-ai/typhoon-ocr2-2b --max-model-len 40960 \
--limit-mm-per-prompt '{"image": 1}' \
--mm-processor-kwargs '{"min_pixels": 2048, "max_pixels": 16777216}'
# Ollama 0.40.15 or newer
ollama pull SuraponOCR/typhoon-ocr2-2b
# llama.cpp (GGUF)
llama-server -hf typhoon-ai/typhoon-ocr2-2b-GGUF:Q8_0 --jinja --reasoning off -c 40960 --image-max-tokens 16384
| Server | base_url |
model |
|---|---|---|
| vLLM | "http://localhost:8000/v1" |
"typhoon-ai/typhoon-ocr2-2b" |
| Ollama | "http://localhost:11434" |
"SuraponOCR/typhoon-ocr2-2b" |
| llama.cpp | "http://localhost:8080/v1" |
"typhoon-ocr2" (llama-server serves one model; any name works) |
ocr = TyphoonOCR(
base_url="http://localhost:8000/v1",
model="typhoon-ai/typhoon-ocr2-2b",
version="v2",
upscale=True,
)
For the smaller page parser, use typhoon-ai/typhoon-ocr2-0.8b (Ollama: SuraponOCR/typhoon-ocr2-0.8b). On vLLM older than 0.33.0, add --enforce-eager to vllm serve: in the default compiled mode of vLLM 0.27.1, accuracy dropped by about 9 points on MDPBench. Other OpenAI-compatible servers work the same way.
What you get back
ocr.parse() returns one PageResult per page:
| Field | Meaning |
|---|---|
page.page |
page number (1-based) |
page.markdown |
the page as Markdown; by default without headers, footers and page footnotes (see below) |
page.elements |
V2 only: every element the model returned, as {"bbox": [x1, y1, x2, y2], "category": "...", "text": "..."}, in reading order |
page.image_size |
(width, height) of the page image the boxes refer to |
page.truncated |
True if the output stopped at the token limit (usually a repetition loop); elements keeps the complete ones |
page.raw |
the model's raw output |
Headers, footers and other categories
The model always returns header, footer and page_footnote elements, and they are always in page.elements. Only page.markdown leaves them out by default, because that is how the model was evaluated (and running page numbers and headers usually get in the way of reading). You choose what the Markdown keeps:
pages = ocr.parse("report.pdf") # default: no header, footer, page_footnote
pages = ocr.parse("report.pdf", keep_header_footer=True) # keep every element
pages = ocr.parse("report.pdf", exclude=["footer"]) # keep headers, drop footers only
pages = ocr.parse("report.pdf", exclude=["header", "footer", "page_footnote", "figure_caption"]) # your own list
footers = [e["text"] for e in pages[0].elements if e["category"] == "footer"] # the elements themselves
On the command line: --keep-header-footer, or --exclude footer (several names allowed; --exclude with no names keeps everything).
The 13 categories are header, title, text, figure, table, formula, figure_caption, table_caption, formula_caption, figure_footnote, table_footnote, page_footnote and footer. Boxes use a 0–1000 grid relative to the page, so they do not depend on the image size:
from typhoon_ocr2 import draw_layout, load_pages, to_pixels
image = load_pages("report.pdf", pages=[1])[0]
first = pages[0].elements[0]
print(first["category"], to_pixels(first["bbox"], *image.size)) # box in pixels
draw_layout(image, pages[0].elements).save("page1_layout.png") # boxes numbered in reading order
Questions and key information (Typhoon OCR V2 2B)
answer = ocr.ask("receipt.jpg", "What is the total amount?") # plain text
fields = ocr.extract("receipt.jpg", ["Date", "Total", "VAT 7%"]) # dict with these keys
everything = ocr.extract("receipt.jpg") # every field; the model picks the keys
The 0.8B model is a page parser only; it is much weaker at questions and extraction. Check extracted values before you use them: key-information extraction is the 2B model's weakest task.
Command line
export TYPHOON_API_KEY=YOUR_KEY
API=https://api.opentyphoon.ai/v1
typhoon-ocr2 report.pdf --base-url $API # Markdown of every page (V2)
typhoon-ocr2 report.pdf --base-url $API --pages 2-4 --format json # layout elements
typhoon-ocr2 receipt.jpg --base-url $API --ask "What is the total amount?"
typhoon-ocr2 receipt.jpg --base-url $API --extract Date Total
typhoon-ocr2 page.jpg --base-url $API --version v1.5 # Typhoon OCR 1.5
typhoon-ocr2 page.jpg --base-url http://localhost:11434 --model SuraponOCR/typhoon-ocr2-2b # Ollama
To avoid repeating --base-url, set TYPHOON_OCR_BASE_URL. Add --no-upscale to turn upscaling off.
Settings
All settings of TyphoonOCR(...). The defaults are the settings the models were evaluated with.
| Setting | Default | What it does |
|---|---|---|
base_url |
$TYPHOON_OCR_BASE_URL, else "http://localhost:8000/v1" |
server address |
model |
on the Typhoon API, chosen from version; on Ollama, "SuraponOCR/typhoon-ocr2-2b"; on other servers, the first model they serve |
model name on the server; needed only for your own server |
api_key |
$TYPHOON_API_KEY |
key for the Typhoon API |
version |
"v2" (or follows model, if you set one) |
"v2" or "v1.5": the prompt and output format |
upscale |
True |
enlarge images whose long side is under 1,600 px. On ThaiOCRBench this raised Typhoon OCR V2 2B from 59.9 to 62.9 and 0.8B from 37.3 to 41.3; large pages are not changed. False turns it off; a number sets another size |
max_tokens |
16384 |
output limit per page |
pdf_long_side |
2048 |
PDF pages are rendered with this long side, in pixels |
max_workers |
4 |
PDF pages sent in parallel |
timeout |
600 |
seconds to wait for one page |
backend |
"auto" |
"openai" or "ollama"; auto picks Ollama for http://localhost:11434 |
figure_language |
"Thai" |
v1.5 only: language of figure descriptions |
The prompts are the ones the models were trained with (typhoon_ocr2.PARSE_PROMPT for V2), and decoding is greedy with thinking turned off.
Coming from typhoon-ocr
typhoon-ocr |
typhoon-ocr2 |
|---|---|
from typhoon_ocr import ocr_document |
from typhoon_ocr2 import ocr_document (same idea), or TyphoonOCR for more |
ocr_document(path, page_num=1, base_url=..., model=...) |
ocr_document(path, pages=1, base_url=..., model=...) |
| returns a Markdown string | TyphoonOCR.parse() also returns layout elements with boxes |
| one page per call | pages="1-3,5" or all pages, sent in parallel |
| needs Poppler for PDFs | no system dependency |
Development
pip install -e ".[dev]"
pytest # unit tests, no GPU needed
TYPHOON_OCR2_TEST_URL=http://localhost:8000/v1 pytest -m integration # against a running server
Releasing
- On pypi.org, add a Trusted Publisher for project
typhoon-ocr2: this GitHub repository, workflowpublish.yml, environmentpypi. - Bump
__version__insrc/typhoon_ocr2/__init__.py, commit, then tag and push:git tag v0.1.0 && git push origin v0.1.0.
License
Apache-2.0. See LICENSE and NOTICE. Use of the Typhoon models is subject to the OpenTyphoon Terms and Conditions.
Metadata
Release files for typhoon-ocr2 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| typhoon_ocr2-0.1.0.tar.gz | 27.5 kB | With provenance | Details for typhoon_ocr2-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| typhoon_ocr2-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for typhoon_ocr2-0.1.0-py3-none-any.whl |
Total release size: 50.4 kB
Release files / typhoon_ocr2-0.1.0.tar.gz
| Download URL | typhoon_ocr2-0.1.0.tar.gz |
|---|---|
| Size | 27.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
468ae8a310e4ccdfd48b35eda7e9a0416286e4ac49813aad69c416ea32d36444
|
|
BLAKE2b-256 checksum How to use checksums |
d0f8707cba9c70bd0e168987a714f82ccf2e6e30e07437379e6b9e248a60baf2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 9, 2026.
Transparency logRelease files / typhoon_ocr2-0.1.0-py3-none-any.whl
| Download URL | typhoon_ocr2-0.1.0-py3-none-any.whl |
|---|---|
| Size | 22.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d72a468c1505b8dfb02ae86b926579912b58d316e3bd7bfee2941c149a86e4dd
|
|
BLAKE2b-256 checksum How to use checksums |
ea062f05c0745e4d90910ae08f8a827d800252cf1ee3ab007fc93ca182ae1f52
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 9, 2026.
Transparency logSecurity for typhoon-ocr2 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for typhoon_ocr2-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 9, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.