windlass.providers.preprocessors.clean¶
clean
¶
Cleaning, language detection and metadata enrichment — all dependency-free.
These are the preprocessors almost every pipeline wants. clean alone
typically removes 5-15% of a PDF corpus's characters (headers, page numbers,
control codes) without losing meaning, which is 5-15% off your embedding bill.
Example
from windlass.core.types import Document doc = Document(content="Hello world\n\n\n\nAgain") CleanPreprocessor(min_length=0).process([doc])[0].content 'Hello world\n\nAgain'
CleanPreprocessor
¶
CleanPreprocessor(
*,
unicode: bool = True,
whitespace: bool = True,
strip_html_tags: bool = False,
remove_urls: bool = False,
remove_page_numbers: bool = True,
normalize_bullets: bool = True,
min_length: int = 20,
max_length: int | None = None,
**config: Any
)
Bases: Preprocessor
Normalises text and removes common noise.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
unicode
|
bool
|
Apply NFKC normalisation and drop control characters. Fixes the ligatures and non-breaking spaces that PDF extraction produces. |
True
|
whitespace
|
bool
|
Collapse runs of spaces and blank lines. |
True
|
strip_html_tags
|
bool
|
Remove HTML tags left over from a mixed-format source. |
False
|
remove_urls
|
bool
|
Delete bare URLs, which embed poorly and add no meaning. |
False
|
remove_page_numbers
|
bool
|
Delete lines that are nothing but a page number. |
True
|
normalize_bullets
|
bool
|
Rewrite Unicode bullets as |
True
|
min_length
|
int
|
Drop documents shorter than this after cleaning. This is how blank pages and extraction artefacts get filtered out. |
20
|
max_length
|
int | None
|
Truncate documents longer than this. |
None
|
**config
|
Any
|
Forwarded to :class: |
{}
|
Example
from windlass.core.types import Document p = CleanPreprocessor(remove_urls=True, min_length=0) p.process([Document(content="see https://x.com now")])[0].content 'see now'
Source code in src\windlass\providers\preprocessors\clean.py
aprocess_one
async
¶
Clean one document.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
document
|
Document
|
The document to clean. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
list[Document]
|
A single-element list, or |
|
than |
list[Document]
|
attr: |
Source code in src\windlass\providers\preprocessors\clean.py
LanguagePreprocessor
¶
LanguagePreprocessor(
*,
allowed: Sequence[str] | None = None,
default: str = "en",
field: str = "language",
**config: Any
)
Bases: Preprocessor
Tags each document with a detected language, and can filter on it.
Mixed-language corpora quietly degrade retrieval: a monolingual embedding model maps text it does not understand to roughly meaningless vectors. Tag first, then either filter or route to a multilingual model.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
allowed
|
Sequence[str] | None
|
Language codes to keep. |
None
|
default
|
str
|
Language assumed when detection is inconclusive. |
'en'
|
field
|
str
|
Metadata key to write the detected code to. |
'language'
|
**config
|
Any
|
Forwarded to :class: |
{}
|
Note
Uses langdetect when installed, and a stop-word plus script
heuristic otherwise. The heuristic is good enough for tagging and
routing, not for anything adversarial.
Example
from windlass.core.types import Document p = LanguagePreprocessor() p.process([Document(content="the quick brown fox is here")])[0].metadata["language"] 'en'
Source code in src\windlass\providers\preprocessors\clean.py
aprocess_one
async
¶
Detect the language and tag (or drop) the document.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
document
|
Document
|
The document to inspect. |
required |
Returns:
| Type | Description |
|---|---|
list[Document]
|
The tagged document, or |
Source code in src\windlass\providers\preprocessors\clean.py
MetadataPreprocessor
¶
MetadataPreprocessor(
*,
extra: dict[str, Any] | None = None,
keywords: int = 8,
estimate_tokens: bool = True,
**config: Any
)
Bases: Preprocessor
Enriches metadata with cheap, useful signals.
Everything here is computed locally with no model call: character and word
counts, an estimated token count, a reading-time estimate, and the top
keywords by frequency. Those fields make retrieval filters (token_count <
2000) and result display far more useful.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
extra
|
dict[str, Any] | None
|
Static key/value pairs merged into every document — a tenant id, a corpus name, an ingestion timestamp. |
None
|
keywords
|
int
|
How many top keywords to extract. |
8
|
estimate_tokens
|
bool
|
Compute a token count. Costs a tokenisation pass. |
True
|
**config
|
Any
|
Forwarded to :class: |
{}
|
Example
from windlass.core.types import Document meta = MetadataPreprocessor().process( ... [Document(content="alpha beta alpha gamma")] ... )[0].metadata meta["word_count"] 4
Source code in src\windlass\providers\preprocessors\clean.py
aprocess_one
async
¶
Compute and attach metadata.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
document
|
Document
|
The document to enrich. |
required |
Returns:
| Type | Description |
|---|---|
list[Document]
|
A single-element list with the enriched document. |