Create an Extraction Plugin
Extraction plugins are the core of document processing in bizSupply. They receive a classified document and an ontology field list, then extract structured data from the document. The extracted fields are validated against the ontology and stored in the platform.
Extraction plugins are the core of document processing in bizSupply. They receive a classified document and an ontology field list, then extract structured data from the document. The extracted fields are validated against the ontology and stored in the platform.
What Extraction Plugins Do
An extraction plugin receives a document (with its raw file bytes) and a list of ontology fields that describe what data to extract. It returns an ExtractionResult mapping field names to their extracted values.
- Extract structured key-value data from unstructured documents
- Use LLM analysis guided by ontology field definitions
- Support multiple field types: string, number, date, boolean, array, and object
- Validate extracted data against field-type constraints
- Return per-field confidence when available
Key method: Return ExtractionResult from extract() — the platform handles field resolution and persistence.
How Extraction Works
The platform executes extraction in a six-step flow:
- Classification — The document is classified (e.g., "invoice") by a classification plugin.
- Ontology Lookup — The platform selects the ontology that matches the classification label.
- Field Resolution — The platform resolves the list of fields defined in the ontology.
- Plugin Dispatch — The platform fetches the document's
file_dataandmime_typeand callsextract(document, file_data, mime_type, fields, configs). - Validation — The platform validates extracted values against field-type constraints.
- Storage — Valid extracted data is persisted and available via the API.
Step 1 — Write the Plugin Code
Create your extraction plugin by extending ExtractionPlugin. The extract() method is async, receives the document, its file bytes, and a list of resolved field definitions, and must return an ExtractionResult.
from bizsupply_sdk import ExtractionPlugin, ExtractionResult
class InvoiceExtractorPlugin(ExtractionPlugin):
"""
Extracts structured data from classified documents.
The platform calls extract() with pre-resolved fields based on document labels.
Just return ExtractionResult - the platform handles persistence.
"""
# Declare configurable parameters as class attributes
configurable_parameters = [
{
"parameter_name": "extraction_prompt_id",
"parameter_type": "str",
"default_value": None,
"description": "Prompt ID for extraction (REQUIRED)",
},
{
"parameter_name": "confidence_threshold",
"parameter_type": "float",
"default_value": 0.8,
"description": "Minimum confidence for extracted values",
},
]
async def extract(
self,
document,
file_data,
mime_type,
fields,
configs,
):
"""
Extract data from a single document.
Args:
document: The document to extract from (already classified)
file_data: Raw file bytes (the LLM reads PDFs directly)
mime_type: MIME type of file_data
fields: Ontology fields to extract (resolved from document.labels)
configs: Runtime configuration (prompt IDs, thresholds, etc.)
Returns:
ExtractionResult with the extracted data
"""
self.logger.info(
f"Extracting from {document.document_id}, "
f"labels={document.labels}, fields={len(fields)}"
)
prompt_id = configs.get("extraction_prompt_id")
if not prompt_id:
self.logger.error("'extraction_prompt_id' config is required")
raise ValueError("extraction_prompt_id configuration is required")
prompt_template = await self.get_prompt(prompt_id)
# Format the ontology fields for the prompt
fields_json = self.format_fields_for_prompt(fields)
prompt = prompt_template.format(
fields=fields_json,
document_content="[See attached document file]",
)
# Call the LLM (await is REQUIRED) — it reads file_data directly
llm_response = await self.prompt_llm(
prompt=prompt,
file_data=file_data,
mime_type=mime_type,
)
if not llm_response:
self.logger.error(f"Empty LLM response for {document.document_id}")
return ExtractionResult(data={})
if isinstance(llm_response, dict):
extracted_data = llm_response
else:
self.logger.error(f"Expected dict, got {type(llm_response)}")
return ExtractionResult(data={})
self.logger.info(
f"Extracted {len(extracted_data)} fields from {document.document_id}"
)
# Return ExtractionResult - the platform handles persistence
return ExtractionResult(
data=extracted_data,
llm_fields=list(extracted_data.keys()),
document_type=document.labels[-1] if document.labels else None,
)Step 2 — Create an Ontology with Fields
Define the fields your extraction plugin will populate. An ontology is a hierarchical taxonomy of label nodes with children; each field uses dtype for its type. Save the taxonomy to a file:
{
"name": "Invoice Ontology",
"taxonomy": {
"label": "document",
"children": [
{
"label": "invoice",
"fields": [
{
"name": "invoice_total",
"dtype": "number",
"description": "Total amount due"
},
{
"name": "invoice_date",
"dtype": "date",
"description": "Date on the invoice"
},
{
"name": "vendor_name",
"dtype": "string",
"description": "Name of the vendor/supplier"
},
{
"name": "line_items",
"dtype": "array",
"description": "List of items with description, quantity, price"
}
]
}
]
}
}Register it with a multipart upload:
curl -X POST "https://api.bizsupply.ai/api/v1/ontologies" \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "name=Invoice Ontology" \
-F "ontology_file=@invoice_ontology.json"See Create an Ontology for the full guide.
Step 3 — Create an Extraction Prompt
Register a reusable prompt template for your extractor by multipart upload, then reference the returned prompt_id through the extraction_prompt_id configurable parameter:
curl -X POST "https://api.bizsupply.ai/api/v1/prompts" \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "name=Invoice Extraction Prompt" \
-F "prompt=@extraction_prompt.txt"A typical prompt asks the model to extract the requested fields and return them as JSON:
Extract the following fields from this document.
Fields to extract:
{fields}
Document:
{document_content}
Respond with JSON containing only the requested field names as keys.Step 4 — Validate and Register
Validate your plugin locally, then submit it for review.
="color:#5c6370;font-style:italic"># Validate the plugin structure
bizsupply validate invoice_extractor.py
="color:#5c6370;font-style:italic"># Submit (multipart — type and configurable parameters are auto-extracted)
curl -X POST "https://api.bizsupply.ai/api/v1/plugins" \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "name=Invoice Extractor" \
-F "description=Extracts structured data from classified documents" \
-F "code=@invoice_extractor.py"Review workflow
Plugin code runs inside an isolated execution environment, so every submission — including from platform administrators — is held in a review queue until approved by a platform administrator. The response tells you where your submission landed:
{"review_id": "...", "status": "pending_review"}Your plugin is not available for use until a platform administrator approves the review via POST /api/v1/reviews/{review_id}/approve. Once approved, the plugin keeps the same ID that survived the submit → approve roundtrip, and pipelines can reference it. There is no bypass — this applies to all plugin types. See Create a Plugin for the full flow.
Key Methods
| Method | Purpose | ||
|---|---|---|---|
await self.prompt_llm(prompt, file_data, mime_type) | Call the LLM for extraction (returns parsed `dict \ | list \ | None`) |
await self.get_prompt(prompt_id) | Load a registered prompt template by ID | ||
self.format_fields_for_prompt(fields) | Format the resolved ontology fields for a prompt | ||
self.logger | Plugin-specific logger (self.logger.info(...), .error(...)) |
The platform handles document fetching, field resolution from the ontology, and data persistence automatically.
Field Types
Ontology fields support the following types. The platform validates extracted values against these types after extraction.
| Type | Python Type | Example Value | Validation | |
|---|---|---|---|---|
string | str | "Acme Corp" | Must be a non-empty string. | |
number | `int \ | float` | 1500.00 | Must be numeric. Strings like "$1,500" are auto-parsed. |
date | str (ISO 8601) | "2026-01-15" | Must be a valid ISO 8601 date string. | |
boolean | bool | true | Must be true or false. | |
array | list[dict] | [{"desc": "Widget", "qty": 10}] | Must be a list. Each item is validated recursively if sub-fields are defined. | |
object | dict | {"street": "123 Main", "city": "NYC"} | Must be a dict. Nested fields are validated against sub-field definitions. |
Common Mistakes
Wrong return type
# WRONG - extract() must return ExtractionResult, not a plain dict
return {"invoice_total": 1500.00}
# CORRECT
from bizsupply_sdk import ExtractionResult
return ExtractionResult(data={"invoice_total": 1500.00})Using the old execute() method
# WRONG - old v1.0 API
async def execute(self, context: PluginContext):
for doc in context.documents:
fields = self.get_ontology_fields_by_labels(doc.labels, context.ontologies)
content = await self.get_document_content(doc)
# ... extract ...
await self.add_document_data(doc, extracted_data)
return context.documents
# CORRECT - implement extract(), return ExtractionResult
async def extract(self, document, file_data, mime_type, fields, configs):
# fields are pre-resolved and file_data is pre-fetched by the platform
result = await self.prompt_llm(prompt=..., file_data=file_data, mime_type=mime_type)
return ExtractionResult(data=result or {})Missing bizsupply_sdk import
# WRONG - ExtractionResult not imported
class MyPlugin(ExtractionPlugin): # NameError!
...
# CORRECT - import both the base class and ExtractionResult
from bizsupply_sdk import ExtractionPlugin, ExtractionResult
class MyPlugin(ExtractionPlugin):
...Pipeline Order
Extraction always runs AFTER classification. The classification label determines which ontology — and therefore which fields — are passed to your extract() method. If a document is not classified, it skips the extraction stage entirely.
A typical pipeline order is: Source → Classification → Extraction. You can have multiple extraction plugins in a pipeline, each handling different document types with different ontologies.
Next Steps
- Create an Ontology — define the field definitions your extractor will populate
- Create a Classification Plugin — route documents to the correct ontology
- Use Plugins — wire classification and extraction together
- Plugin Service API — all available service methods