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 readresult.engine # which engine answeredresult.fell_back # True when the configured engine could not serveresult.blocks # positions, when the engine provides themresult.confidence # when the engine provides itresult.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
| Engine | Where it reads | Cost | Accepts |
|---|---|---|---|
pdf_text | On the machine, nothing leaves | Free | |
plain | On the machine, nothing leaves | Free | PNG, JPEG, TIFF, BMP, WebP |
ai | Through kewos/ai | Metered per call | PNG, 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 guessplain 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
- 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.
- Otherwise, the tenant’s configured engine (
ocr.engine). - If that engine is absent or refuses the file type, the fallback (
ocr.fallback_engine), and the result carriesfell_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.