Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,10 @@ through their typed lanes and do not use discovery.

The original `docpull URL ...` workflow still works: fetch public or explicitly
authorized static/server-rendered web pages and write clean Markdown, NDJSON,
SQLite, or OKF outputs. Project mode adds the persistent evidence lifecycle on
top: sources, runs, diffs, exports, evals, accounting, and local auditability.
SQLite, or Open Knowledge Format (OKF) outputs. OKF is a portable bundle format
that keeps content, indexes, and manifests together for reuse across tools.
Project mode adds the persistent evidence lifecycle on top: sources, runs,
diffs, exports, evals, accounting, and local auditability.

DocPull is local-first: direct fetching, sitemap/link discovery, extraction,
indexing, pack intelligence, and opt-in `agent-browser` rendering can run with
Expand Down Expand Up @@ -580,6 +582,13 @@ installed.
| Hosted extraction APIs | Managed extraction at scale | External dependency and cost |
| docpull | Local public web-source extraction and context packs | No JavaScript rendering by default |

For a tool-by-tool comparison, including self-hosted browser crawlers and
hosted extraction services, see [Alternatives](docs/alternatives.md). Measured
extraction-quality, tokens-per-page, and cost comparisons on a fixed corpus —
plus local extractor baselines — live in the evaluation lab under
[`bench/`](bench/README.md), which is an internal, reproducible harness rather
than a public leaderboard.

## Python SDK

```python
Expand Down Expand Up @@ -674,6 +683,9 @@ part of the package release contract.
When running with `--proxy`, DNS pinning is delegated to the proxy. Pass
`--require-pinned-dns` to refuse that configuration.

See [Compliance Posture](docs/compliance.md) for the full identity, robots.txt,
rate-limit, and AI/TDM opt-out behavior.

## Troubleshooting

```bash
Expand Down
48 changes: 47 additions & 1 deletion bench/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,25 @@ The lab is local-first, deterministic, content-free in reports, and fail-closed
on paid work. Pydantic Evals schedules trials; the framework-neutral schema and
canonical lane scorers remain authoritative.

## Reproduce it

One command replays the committed controlled corpus (212 cases, no network,
no spend) and rescores it:

```bash
uv sync --project bench --locked --dev
uv run --project bench --locked docpull-bench run bench/cases/controlled-v2.yaml \
--adapter replay --system fixture --version 2 \
--replay-dir bench/replays/controlled-v2 --output-dir bench/runs/controlled \
--network-isolation enforced
```

Raw traces land under `bench/runs/controlled/<run-id>/`: `report.json`,
`observations.ndjson`, `scores.ndjson`, and an `artifacts/` directory for
adapters that write files. Reports stay content-free (hashes, lengths,
statuses, metric vectors). This lab informs internal product decisions; it is
not a public leaderboard (see [POSITIONING.md](POSITIONING.md)).

## Rules

- Gold expectations stay in the harness and never cross an adapter boundary.
Expand All @@ -20,8 +39,14 @@ canonical lane scorers remain authoritative.
read.
- Portable reports contain sanitized URLs, hashes, lengths, timings, usage,
costs, statuses, and metric vectors—not fetched bodies.
- New runs write integrity-checked portable report schema v3 and scorer v4;
- New runs write integrity-checked portable report schema v3 and scorer v5;
schema-v2 reports remain readable as legacy history but are never claim-ready.
- Extract and crawl scores carry diagnostic token-economics metrics
(`total_tokens`, `tokens_per_page`, `token_estimator`, and
`html_input_tokens`/`token_reduction_vs_html` when the case maps to
committed fixture HTML). They never gate pass/fail. The estimator is
tiktoken cl100k_base when importable, else a labeled
`max(words, chars/4)` heuristic recorded per score.

## Lanes and corpora

Expand Down Expand Up @@ -81,6 +106,27 @@ The command adapter receives only a minimal base environment plus explicit
`--allow-env` names. Its output cannot override case, system, version, or timing
identity.

## Local OSS baselines

Zero-cost local adapters run popular open-source extractors on the same
committed fixture bytes the controlled corpus serves to live adapters:

| Adapter | Lanes | Notes |
| --- | --- | --- |
| `trafilatura` | extract | Markdown output when the installed release supports it, else plain text. |
| `readability` | extract | readability-lxml main content plus a minimal stdlib HTML→Markdown conversion. |
| `crawl4ai` | extract | Runs Crawl4AI's HTML→Markdown generator on fixture bytes. The crawl lane is not claimed: Crawl4AI crawling requires a live Playwright browser and network, which the controlled replay policy forbids. |

Dependencies are optional. Install them with
`uv sync --project bench --locked --extra baselines`. A missing dependency
yields a failed observation naming the missing package, mirroring hosted
adapters with missing API keys; it never crashes a run.

```bash
uv run --project bench --locked docpull-bench run bench/cases/controlled-v2.yaml \
--adapter trafilatura --system trafilatura --output-dir bench/runs/baselines
```

## Baselines and publication

```bash
Expand Down
10 changes: 5 additions & 5 deletions bench/baselines/controlled-v2.fixture.json
Original file line number Diff line number Diff line change
Expand Up @@ -1912,12 +1912,12 @@
}
},
"environment_label": "local",
"previous_sha256": "6cf972022da3ca80dbcd757addafead9aec2261fa362f940f0c363002919f9a0",
"protocol_sha256": "f22d41f3b5df7c1a9f4d9cc5a61af98b513d68fbdec3ba49aea5aefda1385278",
"reason": "scorer v4 and portable report v3 integrity remediation",
"previous_sha256": "dc9dd194dd7697d9042808fc6d3dd4d64e1a1834d361b5eb22b8aa90c04a384e",
"protocol_sha256": "2e5ffbea044154e4e2e4f196cd4d2d2910228853ac1706afa167bb11fb2315eb",
"reason": "scorer v5 token economics diagnostic metrics",
"schema_version": 2,
"source_report_sha256": "b4eb08033405283fb15795c09f12e84c2f077c44daeac8628d918f30bc8d7d88",
"source_report_sha256": "24c0b2e8b38a25b65f5ee0b5dcc85d9aa0d92d58ee05899f896053e64dac26b5",
"suite_sha256": "21ea6cca08e27dcb7f52d1ebc6b38f969949240576ac9b6a1b13a1aa3374d639",
"system": "fixture",
"updated_at": "2026-07-15T04:57:34.843445+00:00"
"updated_at": "2026-07-22T03:30:30.077676+00:00"
}
7 changes: 7 additions & 0 deletions bench/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ dependencies = [
"pyyaml>=6.0",
]

[project.optional-dependencies]
baselines = [
"crawl4ai>=0.4.0",
"readability-lxml>=0.8.1",
"trafilatura>=1.12.0",
]

[project.scripts]
docpull-bench = "docpull_bench.cli:main"

Expand Down
4 changes: 4 additions & 0 deletions bench/src/docpull_bench/adapters/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@
TavilyGuidedAdvancedCrawlAdapter,
TavilySearchAdapter,
)
from .local_baselines import Crawl4AIAdapter, ReadabilityAdapter, TrafilaturaAdapter
from .replay import ReplayAdapter

__all__ = [
"AdapterError",
"CommandAdapter",
"ContextMarkdownAdapter",
"ContextCrawlAdapter",
"Crawl4AIAdapter",
"DocPullAdapter",
"ExaContentsAdapter",
"ExaFullContentsAdapter",
Expand All @@ -36,11 +38,13 @@
"FirecrawlSearchAdapter",
"ParallelFullExtractAdapter",
"ParallelSearchAdapter",
"ReadabilityAdapter",
"ReplayAdapter",
"SystemAdapter",
"TavilyExtractAdapter",
"TavilyAdvancedExtractAdapter",
"TavilyCrawlAdapter",
"TavilyGuidedAdvancedCrawlAdapter",
"TavilySearchAdapter",
"TrafilaturaAdapter",
]
Loading
Loading