跳到正文
DEV Community· Gaurav Sethi·· 3 小時前AI 評分47

Sloth Drawer:離線智能體讀取設備説明書並為每部設備列印保養卡

Sloth Drawer: an offline agent that reads the manuals you already own and prints one maintenance card per device

AI 導讀

Sloth Drawer 是一款離線、僅用 CPU 運行的智能體,會讀取設備説明書 PDF、核實保養間隔,並為每部設備列印保養卡。19 份空氣清新機説明書的測試找到 35 個間隔,其中 22 個獲核實、13 個經修正、7 個被拒絕,約 10 分鐘完成,費用為 $0。模型只負責提取,驗證器會對照原文重新解析間隔,避免錯誤日期出現在卡片上。

正文

I own 11 devices, and I could not tell you when any of them last needed cleaning — the manual PDFs
are in a drawer nobody opens, and "every 2 months" is information with no memory attached to it. So
I built Sloth Drawer: an offline, CPU-only agent that reads the manual PDFs you already have,
verifies every interval it finds against the manual's own text, and prints one maintenance card per
device
— tape it to the thing, done thinking about it.

And the number that started the whole project: applied to a multi-interval page, a 291 MB model told
me the pre-carbon filter needed replacing "every 1 month" — and cited a sentence that contains no
interval at all. My verifier caught it automatically. That failure is why the tool exists.

What I Built

Sloth Drawer is a Python CLI + a one-page web UI. No API keys, no cloud model, no database you have
to rent. A cold install takes 3.66 seconds (uv with an empty cache, 19 packages) because the core
runs on pypdf and the standard library.

You drop a folder of manual PDFs in (from a checkout, every command runs through uv;
uv tool install . once and the bare sloth-drawer command does the same):

uv run sloth-drawer scan data/manuals      # 19 manuals in 10:14 on a 4-core CPU, unattended
uv run sloth-drawer card honeywell-hpa3000 # one card: HTML (print-ready A6) + 80-column terminal card
uv run sloth-drawer done honeywell-hpa3000 "Filter Replacement"   # you did it — due date moves, in code
uv run sloth-drawer report                 # the accuracy table you see below
uv run sloth-drawer serve                  # the same cards in a browser

The demo corpus is 19 air-purifier manuals across 7 brands — one device class, so one card layout
and one honest, comparable benchmark.

Demo

The live demo serves the actual verified intervals from the scan — a checked, committed snapshot
of the scan database ships inside the container image, and the web card is byte-identical to the CLI
card (there is a test that proves that).

Code

The source code and setup instructions are open source on GitHub:

🦥 Sloth Drawer

Offline agent that reads the manual PDFs you already downloaded and prints one maintenance card per device — the interval, the manufacturer's own sentence, the page number, and the next date it is due.

python licence inference cloud calls cost live demo

Status: the 19-manual air-purifier corpus runs unattended, offline, on the CPU — and the web demo is live at https://sloth-drawer.onrender.com/ (Render free tier built from deploy/, zero env vars). sloth-drawer scan data/manuals yields 35 intervals found · 22 verified · 13 repaired · 7 rejected in ~10 minutes, at $0, on a 4-core box with no GPU. The verifier (extract/verify.py) is the product; the report in out/report.md is the evidence. Built for Hacktoberfest Open-Source AI Challenge: Week 3 — Slow and Steady — post draft staged for review.

➡️ New here? INSTRUCTIONS.md is the complete setup-and-use guide — what to install, which models to pull and why, where…

How I Built It

The pipeline is: ingest → chunk → regex pre-filter → (optional) 270M triage → 1B extraction →
verification
. Three decisions the benchmark forced:

1. One fact per call, flat schema — never an array. I benchmarked three models on 4 CPU cores,
7.5 GB RAM, no GPU. Under an array-of-objects schema, phi4-mini returned {"intervals": []} —
schema-valid, semantically empty. A flat has_interval / task / interval_months / quote / section
schema asked once per interval-shaped sentence passed on every model tested.

2. The model points, the code quotes, the code recomputes. The model returns a quote fragment.
Code then (1) locates the fragment in the page it came from, (2) expands it to the enclosing
sentence, (3) re-parses the interval from that sentence itself, and (4) compares it to the
model's number. Three verdicts: PASS, REPAIR (code value wins, disagreement recorded), REJECT.
Every date on every card is computed in storage/ from a service log you keep. Zero model-authored
dates.
That is the whole trick, and it is why nothing below can be hallucinated into a wrong date.

3. Three-tier cascade. A regex pre-filter scores interval-shaped language for free; on this
corpus only 84 of 1,325 chunks ever reach a model — 94% are filtered out at zero cost.
gemma3:270m (291 MB) is an optional yes/no triage gate; gemma3:1b (806 MB) does the extraction
at temperature 0 with a fixed seed and a structured-output format. Both models run locally via Ollama;
the core install never imports an SDK.

Unit conversions (90 days = 3 months, 720 hours = 1 month, ranges take the midpoint...) live in
one table, stated in the prompt and in the verification code, pinned together by tests — if the
model's rule and the code's rule ever drift, every "every 90 days" becomes a false REPAIR.

The Validator Is the Product

Small local models will lie to you; the job is making the lie unable to reach a printed date.
The verifier's verdicts, with measured examples from bench/ and the shipped run:

Model output Verdict
hallucinated quote ("...every 6 months" — no such text on the page) REJECT (quote_not_in_page)
real quote, wrong number (model said 3, the sentence says 12) REPAIR — code value wins
cites a sentence containing no interval REJECT (no_interval_in_citation)
invented task label — a task on the card that does not appear in the manual text REJECT (task_not_in_source)

A real REPAIR from the run: Winix 5500-2, page 1 — the model said "Clean once every 3 months"
(the washable carbon filter line), but its quoting drifted into the neighbouring "...Last Approx. 12
months True HEPA Filter"
line. The verifier re-parsed the cited sentence, got 12, wrote 12 with
the disagreement recorded. Both the 3-month and the 12-month tasks are on that card today, each
citing its own verified sentence.

The verifier is 554 lines of pure Python with no model calls and no project imports beyond
errors.py — the AST import test enforces that. If you take one idea from this post: an extraction
pipeline does not need a smarter model, it needs a verified quote.
A recent DEV post
lands on the same shape — treat model output as an untrusted API response and gate it — and my
measured numbers agree, with one addition: re-derive the value from the source text the model can
only point at, so even a structurally perfect answer can fail against its own quotation.

The Benchmark

bench/final.py on a synthetic multi-interval page, exact printed output (out/bench-phase3.txt):

Model Size Latency What the verifier did with its answer
gemma3:270m 291 MB 3.6 s claimed 1 month, cited a non-interval sentence → REJECT
gemma3:1b 806 MB 9.1 s months = 2, quote located → PASS
phi4-mini 2.5 GB 53.9 s months = 2, quote located → PASS (6× slower, 3× RAM, uses swap)

gemma3:1b is the default; phi4-mini stays behind --model as a documented alternative. The
failure of the 270M here is not in a prompt somewhere — it is the measured, reproducible reason the
verifier ships with the tool.

Why Does Open Innovation Matter?

Not the generic paragraph — the concrete one:

  • The whole stack runs where you are. 291 MB and 806 MB Gemma weights, 4 CPU cores, no GPU, $0.00 for 22,401 tokens. I proved the full 19-manual run with the network off — inside a user+network namespace with only loopback configured, a second Ollama served inside it, and no route to anything external. Identical verdicts (out/scan-offline.txt). Basements, garages, air-gapped networks: the claim is a log line, not a promise.
  • Swap the model, keep the card. --model phi4-mini changes how the agent behaves with the same interface — deliberately, because open weights are the only thing that makes a model a configuration item and not a dependency.
  • Swap the retrieval backend, keep the card. The same card comes out of the local SQLite FTS5 trigram index and out of a Tiger Cloud hybrid index (pgvector + pg_textsearch BM25, fused with Reciprocal Rank Fusion). A parity run over the live corpus: 4/4 queries PARITY — every local top-5 hit reachable in the cloud top-20. The cloud index is an enhancement, never a prerequisite; the physics of a plane flight cannot break the default.
  • Privacy as a schema constraint, not a promise. The cloud table (manual_chunks) holds chunk text + embedding — it has no column that could hold a device name, serial number, purchase date or service log, and the code that fills it never sees one. Only manual text and embeddings ever cross the wire; the cards and the due dates stay on your machine.
  • The verifier also enforces locality of truth. Because a model must cite a verbatim substring of your own manual, the model can only ever point at text you already own — it cannot smuggle in a "fact" you do not have a reference for.
  • Open formats end to end: SQLite + JSON + Markdown reports, PDFs you already have. Uninstall the tool and you still have everything it extracted, with citations.

My Agent Session

I built this in the open with agentic AI. The Phase 3 wrap-up — the offline proof, the CodeRabbit
triage round, and the documentation decisions — is saved publicly so you can watch the reasoning:

The Lazy Payoff

Reading a maintenance table by hand, for 19 appliances, is about 20 minutes per device when you
care enough to look
, and — being honest — you never look.

Measured payoff instead: drop 19 PDFs into a folder. Walk away. Ten minutes and fourteen seconds
later you have 35 verified intervals, a print-ready card per device, and an accuracy report telling
you exactly which 13 extractions needed repair and why 7 were rejected. ≈37× less reading, $0 of
API cost, forever, offline.
After that, maintenance is one tap (done) or one click in the browser
— and the code, not a language model, moves the due date.

Prize Categories

  • Best Use of Gemma — gemma3:270m triage + gemma3:1b extraction, the measured benchmark, and the 270M's honest failure, all running locally through Ollama.
  • Best Use of Tiger Data — the optional hybrid retrieval backend: pgvector + pg_textsearch BM25, fused with RRF on Tiger Cloud, plus the 4/4 backend parity run.
  • Best Use of Render — https://sloth-drawer.onrender.com/ on the free plan, zero env vars, the seed snapshot shipped in the image.
  • Best Use of Entire (DEV) — this post, and one embedded agent session above.

Repo: https://github.com/Gaurav5189/sloth-drawer · Live demo: https://sloth-drawer.onrender.com/

來源:DEV Community · dev.to