Skip to content

Commit 8d915ba

Browse files
authored
feat!: add content transformers (readability, mdream) and cleanup pipelines (#3)
Add a content-transformers extension family for deterministic, in-process body conversion and the pipelines that use it. Transformers run with no upstream call and can decline unsuitable input. Two deterministic pipelines ship: clean-deterministic (no LLM) and clean-combined (deterministic + LLM summarize). Content transformers - Add the ContentTransformer contract and the transform pipeline step (selects a transformer by name and applies it to the current body). - Add mdream transformer (HTML -> markdown via @mdream/js), shipped as mdream-convert (post-readability conversion) and mdream-aggressive (minimal-preset one-pass extraction). - Add readability transformer: extracts main-article HTML via Mozilla Readability, gated by isProbablyReaderable (minContentLength, minScore, maxElements). Transform outcomes - ContentTransformResult is a discriminated union of transformed | declined; transformers signal "not suitable" instead of forcing a transform. - transform step supports onUnsupported and onDeclined (skip | fail). Truthful Firecrawl media types - rawHtml PDF output is labeled text/plain (not text/html) so HTML-gated transformers skip it correctly. - parsePdf:false returns the raw PDF as a binary body (application/pdf); FIRECRAWL_PARSE_PDF makes this configurable. Pipelines and config - clean-deterministic: firecrawl-html -> readability -> mdream -> truncate. - clean-combined: adds capture-urls -> llm summarize -> verify-urls. - Add PROCESS_CONCURRENCY, READABILITY_*, FIRECRAWL_PARSE_PDF; SOURCE_PROVIDER defaults to empty (use the active pipeline's fallback). Breaking - Rename the shipped docling source instance docling-default -> docling-ocr. Update SOURCE_PROVIDER or custom YAML that referenced docling-default. Docs, env examples, and Compose files updated to match.
1 parent 53781e3 commit 8d915ba

52 files changed

Lines changed: 2276 additions & 124 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.example

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,50 +23,69 @@ LOG_PRETTY=auto
2323

2424
# --- Pipeline ------------------------------------------------------------
2525
# Active pipeline selected by HTTP adapters. Default: truncate (load-source + truncate)
26-
DEFAULT_PIPELINE=truncate
26+
DEFAULT_PIPELINE=clean-combined
2727

28-
# Maximum concurrent source-loading groups. Default: 1
29-
SOURCE_CONCURRENCY=4
28+
# Maximum concurrent source-loading groups. Default: 1; raise only if providers can handle it.
29+
SOURCE_CONCURRENCY=5
3030

31-
# Maximum concurrent LLM workflow groups. Default: 1
31+
# Maximum concurrent processing (transform) groups. Default: 5
32+
PROCESS_CONCURRENCY=5
33+
34+
# Maximum concurrent LLM workflow groups. Default: 1; raise only if the model endpoint can handle it.
3235
LLM_CONCURRENCY=2
3336

34-
# Output renderer used by the default pipeline. Default: debug-xml
37+
# Output renderer override used by shipped pipelines. Default: debug-xml
3538
DEFAULT_OUTPUT_RENDERER=debug-xml
3639

3740
# Include skipped steps in the debug-xml footer. Default: false
3841
DEBUG_XML_INCLUDE_SKIPPED=false
3942

40-
# Desired maximum output size used by summarize and truncate steps. Default: 25000
43+
# Desired maximum output size used by summarize and truncate steps. Default: 25000; larger values return more content.
4144
OUTPUT_TARGET_CHARS=35000
4245

4346
# --- Source provider -----------------------------------------------------
44-
# Active source provider name used by pipelines (truncate and clean-llm). Default: default-http (native HTTP fetch, no external service needed)
45-
SOURCE_PROVIDER=default-http
47+
# Optional source-provider override. Leave empty to use the selected pipeline's fallback.
48+
SOURCE_PROVIDER=
4649

47-
# Firecrawl base URL. Required only when a referenced provider uses it (e.g. default-firecrawl).
50+
# Firecrawl base URL. Required only when a referenced provider uses it (e.g. firecrawl-markdown, firecrawl-html).
4851
FIRECRAWL_BASE_URL=https://firecrawl.example
4952

5053
# Optional Firecrawl bearer token. Default: empty (no token)
5154
FIRECRAWL_API_KEY=
5255

53-
# Docling Serve base URL. Required only when a referenced provider uses it (e.g. default-docling).
56+
# Firecrawl PDF parsing. Set to false to return raw PDF bytes (binary body) instead of parsed text.
57+
FIRECRAWL_PARSE_PDF=
58+
59+
# Docling Serve base URL. Required only when a referenced provider uses it (e.g. docling-ocr).
5460
DOCLING_BASE_URL=https://docling.example
5561

5662
# Optional Docling API key (X-Api-Key header). Default: empty (no token)
5763
DOCLING_API_KEY=
5864

65+
# --- Content transformers ------------------------------------------------
66+
# Post-conversion link and whitespace cleanup for mdream-convert. Ignored when minimal=true (mdream-aggressive). Default: true
67+
MDREAM_CLEAN=true
68+
69+
# Minimum content length for Readability's isProbablyReaderable gate. Default: 140
70+
READABILITY_MIN_CONTENT_CHARS=140
71+
72+
# Minimum readerable score for Readability's isProbablyReaderable gate. Default: 20
73+
READABILITY_MIN_SCORE=20
74+
75+
# Maximum DOM elements Readability parses; 0 = unlimited. Default: 0
76+
READABILITY_MAX_ELEMENTS=0
77+
5978
# --- LLM provider --------------------------------------------------------
60-
# OpenAI-compatible Chat Completions /v1 base URL. Required only when a referenced provider uses it (e.g. default-llm).
79+
# OpenAI-compatible Chat Completions /v1 base URL. Required only when a referenced provider uses it (e.g. llm-default).
6180
LLM_BASE_URL=https://openai-compatible.example/v1
6281

6382
# Optional LLM bearer token. Default: empty (no token)
6483
LLM_API_KEY=
6584

66-
# Model identifier sent to the chat-completions endpoint. Required only when a referenced provider uses it (e.g. default-llm).
85+
# Model identifier sent to the chat-completions endpoint. Required only when a referenced provider uses it (e.g. llm-default).
6786
LLM_MODEL=model-name
6887

69-
# Model context window in tokens. Default: empty (disables the context-fit gate)
88+
# Model context window in tokens. Default: empty (disables the context-fit gate); set to enable it.
7089
LLM_CONTEXT_TOKENS=131072
7190

7291
# Conservative chars-per-token estimate used by the context-fit gate. Default: 3.5
@@ -79,10 +98,10 @@ LLM_SAFETY_MARGIN_TOKENS=128
7998
# Timeout in seconds for the source-loading step. Default: 20
8099
LOAD_SOURCE_TIMEOUT_SECONDS=20
81100

82-
# Timeout in seconds for the clean LLM pass. Default: 60
101+
# Timeout in seconds for the clean LLM pass. Default: 60; raise for slower models.
83102
CLEAN_TIMEOUT_SECONDS=90
84103

85-
# Minimum body length before the clean LLM pass runs. Default: 1000
104+
# Minimum body length before the clean LLM pass runs. Default: 1000; lower values run cleanup more often.
86105
CLEAN_MIN_INPUT_CHARS=500
87106

88107
# Timeout in seconds for the summarize LLM pass. Default: 60

AGENTS.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,13 @@ Read the row that matches your task. Each focused doc is self-contained for its
3737
- When changing behavior, keep the docs that describe it accurate in the same change.
3838
- No gratuitous comments. Inline comments that narrate a change are prohibited ([coding-conventions](docs/agents/coding-conventions.md)). Before adding a comment, ask: will it still be useful after the transient context is gone?
3939
- Don't over-document trivial changes. A new knob, helper, or internal refactor does not warrant updates to `ARCHITECTURE.md` or `extension-authoring.md`. Update only docs that directly describe the changed surface.
40+
41+
## Terminal Behavior on Windows
42+
43+
When using the terminal on Windows:
44+
45+
- Prefer single-line PowerShell commands.
46+
- Avoid interactive commands, pagers, prompts, and commands that wait for input.
47+
- Prefer `pwsh` or PowerShell with `-NoLogo -NoProfile`.
48+
- For git commands, use non-interactive flags such as `--no-pager` and `--no-edit` when appropriate.
49+
- If a command appears stuck after output is printed, do not blindly rerun it. First check whether the command already completed and inspect the visible terminal output.

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ LLM Context Loader is a small HTTP service that turns URLs into markdown for LLM
1111

1212
## Current Shape
1313

14-
- **Pipelines:** `truncate` (default, no LLM) and `clean-llm` (Firecrawl + LLM), selected via `DEFAULT_PIPELINE`.
14+
- **Pipelines:** `truncate` (default, no LLM), `clean-deterministic` (Firecrawl HTML + Readability + mdream, no LLM), `clean-llm` (Firecrawl + LLM), and `clean-combined` (Firecrawl HTML + Readability + mdream + LLM summarize), selected via `DEFAULT_PIPELINE`.
1515
- **Source providers:** native HTTP fetch, Firecrawl, Docling.
16-
- **LLM provider:** OpenAI-compatible `/chat/completions`.
16+
- **Content transformers:** `readability` (article HTML extraction), `mdream` (HTML to markdown).
17+
- **LLM providers:** OpenAI-compatible `/chat/completions`.
1718
- **Output renderers:** `debug-xml` and `passthrough`, selected per pipeline in YAML.
1819

1920
## Quick Start

compose.deploy.yaml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,26 @@ x-llmc-environment: &llmc-environment # Bootstrap environment read before YAML l
1515

1616
# Pipeline concurrency and renderer selection.
1717
SOURCE_CONCURRENCY: "${SOURCE_CONCURRENCY:-1}"
18+
PROCESS_CONCURRENCY: "${PROCESS_CONCURRENCY:-5}"
1819
LLM_CONCURRENCY: "${LLM_CONCURRENCY:-1}"
1920
DEFAULT_OUTPUT_RENDERER: "${DEFAULT_OUTPUT_RENDERER:-debug-xml}"
2021
DEBUG_XML_INCLUDE_SKIPPED: "${DEBUG_XML_INCLUDE_SKIPPED:-false}"
2122
OUTPUT_TARGET_CHARS: "${OUTPUT_TARGET_CHARS:-25000}"
2223

2324
# Source provider.
24-
SOURCE_PROVIDER: "${SOURCE_PROVIDER:-default-http}"
25+
SOURCE_PROVIDER: "${SOURCE_PROVIDER:-}"
2526
FIRECRAWL_BASE_URL: "${FIRECRAWL_BASE_URL:-}"
2627
FIRECRAWL_API_KEY: "${FIRECRAWL_API_KEY:-}"
28+
FIRECRAWL_PARSE_PDF: "${FIRECRAWL_PARSE_PDF:-}"
2729
DOCLING_BASE_URL: "${DOCLING_BASE_URL:-}"
2830
DOCLING_API_KEY: "${DOCLING_API_KEY:-}"
2931

32+
# Content transformers.
33+
MDREAM_CLEAN: "${MDREAM_CLEAN:-true}"
34+
READABILITY_MIN_CONTENT_CHARS: "${READABILITY_MIN_CONTENT_CHARS:-140}"
35+
READABILITY_MIN_SCORE: "${READABILITY_MIN_SCORE:-20}"
36+
READABILITY_MAX_ELEMENTS: "${READABILITY_MAX_ELEMENTS:-0}"
37+
3038
# LLM provider.
3139
LLM_BASE_URL: "${LLM_BASE_URL:-}"
3240
LLM_API_KEY: "${LLM_API_KEY:-}"

compose.yaml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,26 @@ x-llmc-environment: &llmc-environment # Bootstrap environment read before YAML l
1515

1616
# Pipeline concurrency and renderer selection.
1717
SOURCE_CONCURRENCY: "${SOURCE_CONCURRENCY:-1}"
18+
PROCESS_CONCURRENCY: "${PROCESS_CONCURRENCY:-5}"
1819
LLM_CONCURRENCY: "${LLM_CONCURRENCY:-1}"
1920
DEFAULT_OUTPUT_RENDERER: "${DEFAULT_OUTPUT_RENDERER:-debug-xml}"
2021
DEBUG_XML_INCLUDE_SKIPPED: "${DEBUG_XML_INCLUDE_SKIPPED:-false}"
2122
OUTPUT_TARGET_CHARS: "${OUTPUT_TARGET_CHARS:-25000}"
2223

2324
# Source provider.
24-
SOURCE_PROVIDER: "${SOURCE_PROVIDER:-default-http}"
25+
SOURCE_PROVIDER: "${SOURCE_PROVIDER:-}"
2526
FIRECRAWL_BASE_URL: "${FIRECRAWL_BASE_URL:-}"
2627
FIRECRAWL_API_KEY: "${FIRECRAWL_API_KEY:-}"
28+
FIRECRAWL_PARSE_PDF: "${FIRECRAWL_PARSE_PDF:-}"
2729
DOCLING_BASE_URL: "${DOCLING_BASE_URL:-}"
2830
DOCLING_API_KEY: "${DOCLING_API_KEY:-}"
2931

32+
# Content transformers.
33+
MDREAM_CLEAN: "${MDREAM_CLEAN:-true}"
34+
READABILITY_MIN_CONTENT_CHARS: "${READABILITY_MIN_CONTENT_CHARS:-140}"
35+
READABILITY_MIN_SCORE: "${READABILITY_MIN_SCORE:-20}"
36+
READABILITY_MAX_ELEMENTS: "${READABILITY_MAX_ELEMENTS:-0}"
37+
3038
# LLM provider.
3139
LLM_BASE_URL: "${LLM_BASE_URL:-}"
3240
LLM_API_KEY: "${LLM_API_KEY:-}"

config/llm-context-loader.yaml

Lines changed: 122 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,24 +26,31 @@ outputRenderers:
2626
type: passthrough
2727
config: {}
2828

29-
# Source providers turn an input URL into source markdown.
29+
# Source providers turn an input URL into a pipeline body.
3030
sourceProviders:
31-
default-http:
31+
http-default:
3232
type: http
3333
config:
3434
userAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"
3535
maxBytes: 5000000
3636
titleFromHtml: true
37-
default-firecrawl:
37+
firecrawl-html:
38+
type: firecrawl
39+
config:
40+
baseUrl: ${FIRECRAWL_BASE_URL:-}
41+
apiKey: ${FIRECRAWL_API_KEY:-}
42+
output: rawHtml
43+
parsePdf: ${FIRECRAWL_PARSE_PDF:-true}
44+
firecrawl-markdown:
3845
type: firecrawl
3946
config:
4047
baseUrl: ${FIRECRAWL_BASE_URL:-}
4148
apiKey: ${FIRECRAWL_API_KEY:-}
4249
output: markdown
4350
onlyMainContent: true
4451
stripBase64Images: true
45-
parsePdf: true
46-
default-docling:
52+
parsePdf: ${FIRECRAWL_PARSE_PDF:-true}
53+
docling-ocr:
4754
type: docling
4855
config:
4956
baseUrl: ${DOCLING_BASE_URL:-}
@@ -52,9 +59,26 @@ sourceProviders:
5259
doOcr: true
5360
tableMode: accurate
5461

62+
# Content transformers rewrite the body between representations (e.g. HTML to markdown).
63+
contentTransformers:
64+
readability-default:
65+
type: readability
66+
config:
67+
minContentLength: ${READABILITY_MIN_CONTENT_CHARS:-140}
68+
minScore: ${READABILITY_MIN_SCORE:-20}
69+
maxElements: ${READABILITY_MAX_ELEMENTS:-0}
70+
mdream-convert:
71+
type: mdream
72+
config:
73+
clean: ${MDREAM_CLEAN:-true}
74+
mdream-aggressive:
75+
type: mdream
76+
config:
77+
minimal: true
78+
5579
# LLM providers run prompt-rendered chat-completions passes.
5680
llmProviders:
57-
default-llm:
81+
llm-default:
5882
type: openai-chat
5983
config:
6084
baseUrl: ${LLM_BASE_URL:-}
@@ -77,7 +101,38 @@ pipelines:
77101
concurrencyGroup: source
78102
timeoutSeconds: ${LOAD_SOURCE_TIMEOUT_SECONDS:-20}
79103
config:
80-
provider: ${SOURCE_PROVIDER:-default-http}
104+
provider: ${SOURCE_PROVIDER:-http-default}
105+
- type: truncate
106+
name: truncate
107+
config:
108+
targetChars: ${OUTPUT_TARGET_CHARS:-25000}
109+
110+
clean-deterministic:
111+
outputRenderer: ${DEFAULT_OUTPUT_RENDERER:-debug-xml}
112+
limiters:
113+
source: ${SOURCE_CONCURRENCY:-1}
114+
process: ${PROCESS_CONCURRENCY:-5}
115+
steps:
116+
- type: load-source
117+
name: fetch
118+
concurrencyGroup: source
119+
timeoutSeconds: ${LOAD_SOURCE_TIMEOUT_SECONDS:-20}
120+
config:
121+
provider: ${SOURCE_PROVIDER:-firecrawl-html}
122+
- type: transform
123+
name: clean
124+
concurrencyGroup: process
125+
config:
126+
transformer: readability-default
127+
target: text/html
128+
emitDiagnostics: true
129+
- type: transform
130+
name: convert
131+
concurrencyGroup: process
132+
config:
133+
transformer: mdream-convert
134+
target: text/markdown
135+
emitDiagnostics: true
81136
- type: truncate
82137
name: truncate
83138
config:
@@ -94,7 +149,7 @@ pipelines:
94149
concurrencyGroup: source
95150
timeoutSeconds: ${LOAD_SOURCE_TIMEOUT_SECONDS:-20}
96151
config:
97-
provider: ${SOURCE_PROVIDER:-default-firecrawl}
152+
provider: ${SOURCE_PROVIDER:-firecrawl-markdown}
98153
- type: capture-urls
99154
name: capture_source_urls
100155
concurrencyGroup: source
@@ -105,7 +160,7 @@ pipelines:
105160
concurrencyGroup: llm
106161
timeoutSeconds: ${CLEAN_TIMEOUT_SECONDS:-60}
107162
config:
108-
provider: default-llm
163+
provider: llm-default
109164
minInputChars: ${CLEAN_MIN_INPUT_CHARS:-1000}
110165
outputReserveRatio: 1.0
111166
templates:
@@ -123,7 +178,64 @@ pipelines:
123178
concurrencyGroup: llm
124179
timeoutSeconds: ${SUMMARIZE_TIMEOUT_SECONDS:-60}
125180
config:
126-
provider: default-llm
181+
provider: llm-default
182+
minInputChars: ${OUTPUT_TARGET_CHARS:-25000}
183+
outputReserveChars: ${OUTPUT_TARGET_CHARS:-25000}
184+
templates:
185+
system: ../templates/summarize.system.md
186+
user: ../templates/summarize.user.md
187+
vars:
188+
targetChars: ${OUTPUT_TARGET_CHARS:-25000}
189+
- type: verify-urls
190+
name: verify_after_summarize
191+
concurrencyGroup: llm
192+
config:
193+
artifact: trusted-urls
194+
onHallucination: report
195+
maxReportedUrls: 50
196+
- type: truncate
197+
name: truncate
198+
config:
199+
targetChars: ${OUTPUT_TARGET_CHARS:-25000}
200+
201+
clean-combined:
202+
outputRenderer: ${DEFAULT_OUTPUT_RENDERER:-debug-xml}
203+
limiters:
204+
source: ${SOURCE_CONCURRENCY:-1}
205+
process: ${PROCESS_CONCURRENCY:-5}
206+
llm: ${LLM_CONCURRENCY:-1}
207+
steps:
208+
- type: load-source
209+
name: fetch
210+
concurrencyGroup: source
211+
timeoutSeconds: ${LOAD_SOURCE_TIMEOUT_SECONDS:-20}
212+
config:
213+
provider: ${SOURCE_PROVIDER:-firecrawl-html}
214+
- type: transform
215+
name: clean
216+
concurrencyGroup: process
217+
config:
218+
transformer: readability-default
219+
target: text/html
220+
emitDiagnostics: true
221+
- type: transform
222+
name: convert
223+
concurrencyGroup: process
224+
config:
225+
transformer: mdream-convert
226+
target: text/markdown
227+
emitDiagnostics: true
228+
- type: capture-urls
229+
name: capture_source_urls
230+
concurrencyGroup: process
231+
config:
232+
artifact: trusted-urls
233+
- type: llm-pass
234+
name: summarize
235+
concurrencyGroup: llm
236+
timeoutSeconds: ${SUMMARIZE_TIMEOUT_SECONDS:-60}
237+
config:
238+
provider: llm-default
127239
minInputChars: ${OUTPUT_TARGET_CHARS:-25000}
128240
outputReserveChars: ${OUTPUT_TARGET_CHARS:-25000}
129241
templates:

0 commit comments

Comments
 (0)