Skip to content

Commit f006261

Browse files
committed
refactor(transformers): drop observability diagnostics and disambiguate readability decline
Content transformers no longer emit observability diagnostics: the readability and mdream char-count summaries and the empty_output flag are removed. mdream returns empty markdown as a transformed empty body, so a content-free page fails the run via the empty-body rollup. Readability's empty-article decline is renamed parse_empty -> output_empty, distinct from the no-document parse_empty case. Remove the now-inert emitDiagnostics flags from the shipped pipelines and sync the docs.
1 parent 179b0fa commit f006261

8 files changed

Lines changed: 16 additions & 37 deletions

File tree

config/llm-context-loader.yaml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,14 +125,12 @@ pipelines:
125125
config:
126126
transformer: readability-default
127127
target: text/html
128-
emitDiagnostics: true
129128
- type: transform
130129
name: convert
131130
concurrencyGroup: process
132131
config:
133132
transformer: mdream-convert
134133
target: text/markdown
135-
emitDiagnostics: true
136134
- type: truncate
137135
name: truncate
138136
config:
@@ -217,14 +215,12 @@ pipelines:
217215
config:
218216
transformer: readability-default
219217
target: text/html
220-
emitDiagnostics: true
221218
- type: transform
222219
name: convert
223220
concurrencyGroup: process
224221
config:
225222
transformer: mdream-convert
226223
target: text/markdown
227-
emitDiagnostics: true
228224
- type: capture-urls
229225
name: capture_source_urls
230226
concurrencyGroup: process

docs/CUSTOMIZATION.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ config:
203203

204204
Supports text `text/html` or `application/xhtml+xml` bodies targeting `text/html`. The incoming title is preserved; when absent, the extracted article title is used instead.
205205

206-
Decline reasons are `not_readerable` and `parse_empty`. Runtime errors propagate to the orchestrator; the failed step has no effect, so downstream steps continue from the prior body and the run rolls up as `degraded`.
206+
Decline reasons are `not_readerable`, `parse_empty`, and `output_empty`. Runtime errors propagate to the orchestrator; the failed step has no effect, so downstream steps continue from the prior body and the run rolls up as `degraded`.
207207

208208
### `mdream`
209209

@@ -349,7 +349,7 @@ Applies a named content transformer to the current body. The step resolves `tran
349349
| `transformer` | string | — | Name of a configured `contentTransformers` instance. Required. |
350350
| `target` | string | — | Media type the transformer must produce, for example `text/markdown`. Required. |
351351
| `onUnsupported` | `skip` \| `fail` | `skip` | What to do when the transformer does not support the current body (wrong source media type or representation). |
352-
| `onDeclined` | `skip` \| `fail` | `skip` | What to do when the transformer returns `declined` (e.g. `not_readerable`, `parse_empty`). |
352+
| `onDeclined` | `skip` \| `fail` | `skip` | What to do when the transformer returns `declined`. |
353353
| `emitDiagnostics` | bool | `false` | When enabled, transformer-reported diagnostics are surfaced as child nodes in the step report. |
354354

355355
The step skips with `no_body` when there is no body. When the transformer does not support the current body it skips with `unsupported` (or fails with `onUnsupported: fail`). When the transformer returns `declined`, the step skips with that reason (or fails with `onDeclined: fail`). A transform aborted by the step timeout fails with `timeout`. If the transformer returns a body that does not match `target`, the step fails with `wrong_output_type`.

docs/agents/extension-authoring.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ The result type is a discriminated union:
7474

7575
`supports` gates `transform`: the step only invokes a matching transformer, so `transform` may throw `InternalError` for inputs that bypass the gate. Use `declined` for deliberate "not suitable" decisions (e.g. `isProbablyReaderable` returned false).
7676

77-
Built-in examples: `src/builtins/content-transformers/readability/` (article HTML extraction, outputs `declined`), `src/builtins/content-transformers/mdream/` (HTML to markdown, always `transformed`).
77+
Built-in examples: `src/builtins/content-transformers/readability/` (article HTML extraction, can return `declined`), `src/builtins/content-transformers/mdream/` (HTML to markdown, always `transformed`).
7878

7979
### LLM Providers
8080

docs/agents/smoke-testing.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ For the OWUI surface the per-URL time column is the **batch** time shared by eve
104104

105105
When the script output is not enough, read the server logs: pipeline start/finish lines carry the URL, outcome, and duration, and step warnings carry a `reason`. To dig into a single URL, use `inspect-suspects.ps1`.
106106

107-
A `result="failed"` with `final_length=0` does not imply a pipeline bug by itself. Check the step entries in the footer: was the fetch step `ok`? Did all steps that should run actually run? A footer step showing `empty_output` is normal for 404 pages, paywalled content, or JavaScript-rendered SPAs — the pipeline cannot salvage those. Look at each step's `status` and `reason` before deciding something is wrong.
107+
A `result="failed"` with `final_length=0` does not imply a pipeline bug by itself. Check the step entries in the footer: was the fetch step `ok`? Did all steps that should run actually run? An empty final body is normal for 404 pages, paywalled content, or JavaScript-rendered SPAs — the deterministic transformers convert them to empty markdown and the pipeline cannot salvage those. Look at each step's `status` and `reason` before deciding something is wrong.
108108

109109
## Common Traps
110110

src/builtins/content-transformers/mdream/mdream-transformer.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
* passes the source URL as the mdream `origin` so relative links and images
66
* resolve to absolute URLs. The `minimal` preset additionally isolates main
77
* content and filters boilerplate; `clean` applies link and whitespace cleanup.
8+
* Empty conversion output is returned as a valid empty body, not a decline, so
9+
* a content-free page (e.g. a script-only SPA shell) fails the run honestly.
810
*/
911

1012
import { htmlToMarkdown, withMinimalPreset, type MdreamOptions } from "@mdream/js";
@@ -13,7 +15,6 @@ import { InternalError } from "../../../shared/errors.js";
1315
import { isHtmlMediaType, mediaTypes } from "../../../shared/media-types.js";
1416

1517
import type {
16-
ContentTransformDiagnostic,
1718
ContentTransformRequest,
1819
ContentTransformResult,
1920
ContentTransformer
@@ -54,15 +55,10 @@ export class MdreamTransformer implements ContentTransformer {
5455
throw new InternalError("mdream transformer requires a text body", "mdream_non_text_body");
5556

5657
const markdown = htmlToMarkdown(body.content, this.buildOptions(input.url)).trim();
57-
const diagnostics: ContentTransformDiagnostic[] = [
58-
{ code: "mdream", message: `${body.content.length} html chars -> ${markdown.length} markdown chars` }
59-
];
60-
if (!markdown) diagnostics.push({ code: "empty_output" });
6158

6259
return {
6360
outcome: "transformed",
64-
body: { kind: "text", mediaType: mediaTypes.markdown, content: markdown, title: body.title },
65-
diagnostics
61+
body: { kind: "text", mediaType: mediaTypes.markdown, content: markdown, title: body.title }
6662
};
6763
}
6864

src/builtins/content-transformers/readability/readability-transformer.ts

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,7 @@ import { parseHTML } from "linkedom";
1515
import { InternalError } from "../../../shared/errors.js";
1616
import { isHtmlMediaType, mediaTypes } from "../../../shared/media-types.js";
1717

18-
import type {
19-
ContentTransformDiagnostic,
20-
ContentTransformResult,
21-
ContentTransformer
22-
} from "../../../contracts/extensions/content-transformer.js";
18+
import type { ContentTransformResult, ContentTransformer } from "../../../contracts/extensions/content-transformer.js";
2319
import type { BodyContent } from "../../../contracts/pipeline/context.js";
2420
import type { Logger } from "../../../shared/logger.js";
2521
import type { ReadabilityTransformerConfig } from "./readability-transformer-config.js";
@@ -70,16 +66,7 @@ export class ReadabilityTransformer implements ContentTransformer {
7066
return { outcome: "declined", reason: "not_readerable" };
7167

7268
const article = new Readability(doc, { maxElemsToParse: this.config.maxElements || undefined }).parse();
73-
if (!article || !article.content) return { outcome: "declined", reason: "parse_empty" };
74-
75-
const originalLength = body.content.length;
76-
const articleLength = article.content.length;
77-
const diagnostics: ContentTransformDiagnostic[] = [
78-
{
79-
code: "readability",
80-
message: `${originalLength} html chars -> ${articleLength} article chars`
81-
}
82-
];
69+
if (!article || !article.content) return { outcome: "declined", reason: "output_empty" };
8370

8471
return {
8572
outcome: "transformed",
@@ -88,8 +75,7 @@ export class ReadabilityTransformer implements ContentTransformer {
8875
mediaType: mediaTypes.html,
8976
content: article.content,
9077
title: body.title || (article.title ?? undefined)
91-
},
92-
diagnostics
78+
}
9379
};
9480
}
9581
}

tests/builtins/content-transformers/mdream/mdream-transformer.test.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ describe("MdreamTransformer.transform", () => {
8282
expect(result.body.content).toContain("](https://example.com");
8383
});
8484

85-
it("emits a summary diagnostic and flags empty output", async () => {
85+
it("returns a transformed empty body when conversion yields no markdown", async () => {
8686
const transformer = makeTransformer();
8787
const empty = await transformer.transform(
8888
{
@@ -94,8 +94,9 @@ describe("MdreamTransformer.transform", () => {
9494
);
9595
expect(empty.outcome).toBe("transformed");
9696
if (empty.outcome !== "transformed") throw new Error("Expected transformed outcome");
97-
expect(empty.diagnostics?.some(diagnostic => diagnostic.code === "empty_output")).toBe(true);
98-
expect(empty.diagnostics?.some(diagnostic => diagnostic.code === "mdream")).toBe(true);
97+
expect(empty.body.kind).toBe("text");
98+
if (empty.body.kind !== "text") throw new Error("Expected text body");
99+
expect(empty.body.content).toBe("");
99100
});
100101

101102
it("throws when given a non-text body", async () => {

tests/builtins/content-transformers/readability/readability-transformer.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ describe("ReadabilityTransformer.transform", () => {
9595
expect(result.body.title).toBe("Page Title");
9696
});
9797

98-
it("adds a readability diagnostic with char counts", async () => {
98+
it("transforms article HTML without diagnostics", async () => {
9999
const transformer = makeTransformer();
100100
const body: BodyContent = {
101101
kind: "text",
@@ -107,7 +107,7 @@ describe("ReadabilityTransformer.transform", () => {
107107

108108
expect(result.outcome).toBe("transformed");
109109
if (result.outcome !== "transformed") return;
110-
expect(result.diagnostics?.some(d => d.code === "readability")).toBe(true);
110+
expect(result.diagnostics).toBeUndefined();
111111
});
112112

113113
it("declines not_readerable for short boilerplate HTML", async () => {

0 commit comments

Comments
 (0)