Customs Declaration Extraction (profy-customs)
Turn trade paperwork — contracts, commercial invoices, bills of lading, packing lists, bonded-zone manifests — into a customs declaration JSON where every field is traceable and machine-checkable. Each field carriesvalue, reason, confidence, and page, so you can see which sentence on which page produced it instead of receiving a pile of numbers you have no way to audit.
This is a vertical-domain plugin, not a general OCR wrapper. It ships China Customs field definitions, the five standard code tables, cross-field consistency rules, and a set of disambiguation decision trees. General OCR gives you text; this gives you declarable fields.
Enabling it
profy-customs is user_selectable: true — you must tick it in the plugin panel for a conversation. It never joins on its own.
Its activation block contains only user_selectable. There is no requires and no conditions, which means once selected it works in any sandbox mode. It does not need Desktop, and it does not need a browser.
desktop_connected: true. Customs has no such gate — it is pure server-side computation plus one HTTP call to the OCR service.
Quick start
Upload the documents, then say:
Routing on intent matters because the full pipeline is expensive. A question about one field should not trigger a seven-step extraction.
The three tools
customs_ocr — layout-aware text recognition
bbox is the reason this tool exists. Most of the information on a customs document lives inside tables, and a flat text stream loses “which column does this number belong to”. Coordinates let the extraction step reconstruct the table structure.
Service endpoint is controlled by CUSTOMS_OCR_URL, defaulting to http://profy-ocr:8001. Requests go to /ocr/parse with a 120-second per-request timeout.
customs_code_lookup — standard code tables
This is a deterministic lookup — the design intent is that the model never guesses a code value. Exact matching compares against both
name and any aliases on the entry. Setting fuzzy=True switches to substring matching over the serialized entry and returns at most 10 results, a hard cap that keeps tool output from flooding the context window.
customs_validate — format and cross-field checks
passed is computed as P0 count == 0 AND P1 count == 0. P2 and P3 findings are reported but do not block. The severity ladder maps to real customs rejection risk: P0 means the declaration will be rejected, P1 means it likely will, P2 is a consistency smell worth a human look, P3 is informational.
The full field set (83 fields)
56 header fields plus 27 body fields. Fields are grouped, and the skill loads only the reference for the group it is currently working on:
The food and hazard groups are conditionally activated. Food fields load when the HS code’s regulatory conditions include A or B; hazard fields load when an MSDS is present and its section 14 carries a dangerous goods classification. Loading all groups unconditionally would waste context on the majority of shipments that are neither.
Scenario detection
Before extracting anything, the skill classifies the shipment, because the same field means different things across scenarios:Validation rules in full
Format validation
Messages are emitted in Chinese because they mirror what a Chinese customs broker expects to read. Date validation goes beyond digit count: the year must fall in 1900–2099, the month in 1–12, and the day is bounded by
calendar.monthrange for that specific year and month. So 20250230 fails with 日期30超出2025年2月最大天数28 rather than passing a naive regex.
Cross-field consistency
These encode the arithmetic a customs broker does by hand. CIF already includes freight and insurance in the price, so declaring them separately double-counts — hence P1 rather than a warning.
How extraction is structured
Phase split
Three-tier context loading
Reference material is loaded and released deliberately rather than dumped up front:
The lock file is what makes long extractions survivable. Committed field values are written to disk, so a session that runs long does not have to keep every intermediate result in the context window, and a resumed session picks up where the previous one stopped.
Ten disambiguation decision trees
Each tree resolves to a deterministic choice plus a confidence adjustment, which is why the output carries
confidence per field rather than one number for the document.
Boundaries and failure modes
Other boundaries worth knowing before you rely on this in production:- Cross-field checks only run over header fields. Body rows get per-field format validation only; relationships between rows are not checked.
- Fuzzy lookup truncates silently at 10 results. The returned
countis the post-truncation count, so it cannot be used to detect that truncation happened. - An unknown table name returns a structured error,
{"error": "未知代码表 'X'", "available_tables": [...]}, rather than raising. All three tools return errors as JSON — they never throw into the agent loop. - Large consignments should be split across sessions. Beyond roughly 8 body rows, the raw document text alone will dominate the context window.
Troubleshooting
Where the result goes
The final JSON is written by Core intomessage.metadata.declaration when the conversation reaches complete, mapped to the preview contract {basic, fields, goods, ...}. This is the persistence contract between the plugin and the product: the declaration preview panel in the UI reads exactly this. Do not invent parallel field names such as draftResult or declarationForm — nothing downstream reads them.
Verify your setup
- With the plugin ticked, run a lookup with no file attached: “What is the supervision method code for general trade?” A response of
代码表 ... 为空或文件不存在means you have hit the known defect above. - Upload a single-page invoice and say “extract only the identity field group”. You should see the OCR card expand, and each field should carry
pageandconfidence. - Feed the extraction result back with “validate this”. The validation card should render a
by_severitybreakdown —passed: truehere is the signal that the whole chain works.
Related
Plugin overview
All 28 built-in plugins and how they activate
Skills
How skills load on demand instead of filling the context up front
Verified 2026-08-11. Sources:
services/agent-runtime/src/plugins/builtin/customs/plugin.json, tools/customs_ocr.py, tools/customs_code_lookup.py, tools/customs_validate.py, skills/customs-extraction/SKILL.md.
