Skip to content

Reading a document (OCR)

kewos/ocr turns an image into text. A module calls one function; it never picks an engine, never names a provider, and does not need to know whether the instance is online.

The call

from kewos.ocr import extract_text
result = extract_text(
document, # bytes
mime_type="image/jpeg",
default=tenant_default, # the tenant's ocr.engine parameter
fallback=tenant_fallback, # the tenant's ocr.fallback_engine parameter
)
result.text # what was read
result.engine # which engine answered
result.fell_back # True when the configured engine could not serve
result.blocks # positions, when the engine provides them
result.confidence # when the engine provides it

result.engine is not decoration. A module deriving an amount from read text must be able to say where that text came from, and one day somebody will ask.

The three engines

EngineWhere it readsCostAccepts
pdf_textOn the machine, nothing leavesFreePDF
plainOn the machine, nothing leavesFreePNG, JPEG, TIFF, BMP, WebP
aiThrough kewos/aiMetered per callPNG, JPEG, WebP, GIF

PDF, and why it is not OCR

Most business PDFs are not scans. An invoice produced by accounting software, an e-invoice, an exported statement: all carry a real text layer. Reading it is exact, not probabilistic, costs nothing, and needs neither a native toolchain nor a model.

That is why pdf_text sits in front of OCR rather than beside it: paying a vision model to read a document that already says what it contains is spending money to get a worse answer.

result = extract_text(pdf_bytes, mime_type="application/pdf", default="pdf_text")
result.text # pages, separated by a form feed (\f)
result.confidence # None: a text layer is not a guess

plain relies on a system binary Kewos does not depend on: most installations will never do OCR, and making every deployment carry a native toolchain for a feature it does not use is a cost paid by all for the benefit of a few. The engine probes at registration; binary absent, engine not registered, installation intact, and the capability reported as off.

ai goes through the AI facade and never touches a provider directly. That is the whole design: the provider allowlist, the data-residency consent, the quota, the audit trail and the cache exist once, in the facade, and this engine inherits them by not going round it.

Selection, and why it is strict

  1. An explicitly named engine wins, or the call fails. There is no fallback: naming an engine is a deliberate act, and quietly serving another one would answer a question nobody asked.
  2. Otherwise, the tenant’s configured engine (ocr.engine).
  3. If that engine is absent or refuses the file type, the fallback (ocr.fallback_engine), and the result carries fell_back=True.

The degraded mode is visible

from kewos.ocr import ocr_capability
ocr_capability(configured_engine="plain", fallback_engine="ai")
# {"enabled": True, "engine": "ai", "degraded": True,
# "offline": False, "costly": True, "reason": "configured-engine-missing"}

Two facts matter to an operator deciding whether to switch OCR on, and both are there: does it work, and do documents leave the machine.

The case that catches everybody is this one: the binary is installed, so the engine exists, but nobody selected it. The capability then answers enabled: false with reason: "no-engine-configured", rather than showing a button that will fail on the first click.

What the subsystem does not do

  • It stores nothing. The document stays with the caller.
  • It never logs read text, nor an extract, nor a length that would betray content. The log carries the engine, the type and the outcome.
  • It interprets nothing. Finding a VAT number in the text is a module’s job.
  • It does not run inside an HTTP request for a real document. A read is slow and, on the AI side, outbound: modules put it on the job queue.

Adding an engine

from kewos.ocr import OcrEngine, register_engine
register_engine(
OcrEngine(
name="my_engine",
read=my_callable, # (document, mime_type, language) -> OcrResult
accepts=("image/png",),
offline=True, # does the document stay on this machine?
costly=False, # does a read have a per-call price?
)
)

offline and costly are not documentation: selection reads them to refuse picking a remote, metered engine on its own.