rustai 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. rustai-0.1.0/.gitignore +32 -0
  2. rustai-0.1.0/CHANGELOG.md +162 -0
  3. rustai-0.1.0/CONTRIBUTING.md +58 -0
  4. rustai-0.1.0/Cargo.lock +2286 -0
  5. rustai-0.1.0/Cargo.toml +78 -0
  6. rustai-0.1.0/LICENSE-APACHE +202 -0
  7. rustai-0.1.0/LICENSE-MIT +21 -0
  8. rustai-0.1.0/PKG-INFO +485 -0
  9. rustai-0.1.0/README.md +455 -0
  10. rustai-0.1.0/examples/bench.rs +86 -0
  11. rustai-0.1.0/examples/profile.rs +68 -0
  12. rustai-0.1.0/examples/research.py +55 -0
  13. rustai-0.1.0/notebooks/colab_oneshot.ipynb +286 -0
  14. rustai-0.1.0/notebooks/colab_oneshot.py +259 -0
  15. rustai-0.1.0/notebooks/colab_quickstart.ipynb +673 -0
  16. rustai-0.1.0/pyproject.toml +46 -0
  17. rustai-0.1.0/python/rustai/__init__.py +102 -0
  18. rustai-0.1.0/python/rustai/_rustai.pyi +256 -0
  19. rustai-0.1.0/python/rustai/py.typed +0 -0
  20. rustai-0.1.0/rustfmt.toml +6 -0
  21. rustai-0.1.0/src/denoise/mod.rs +549 -0
  22. rustai-0.1.0/src/error.rs +103 -0
  23. rustai-0.1.0/src/http/browser.rs +99 -0
  24. rustai-0.1.0/src/http/mod.rs +1110 -0
  25. rustai-0.1.0/src/http/robots.rs +223 -0
  26. rustai-0.1.0/src/lib.rs +109 -0
  27. rustai-0.1.0/src/parse/dom.rs +487 -0
  28. rustai-0.1.0/src/parse/index.rs +581 -0
  29. rustai-0.1.0/src/parse/markdown.rs +732 -0
  30. rustai-0.1.0/src/parse/meta.rs +200 -0
  31. rustai-0.1.0/src/parse/mod.rs +687 -0
  32. rustai-0.1.0/src/pipeline.rs +193 -0
  33. rustai-0.1.0/src/py.rs +1042 -0
  34. rustai-0.1.0/src/rank/bm25.rs +150 -0
  35. rustai-0.1.0/src/rank/density.rs +216 -0
  36. rustai-0.1.0/src/rank/mod.rs +9 -0
  37. rustai-0.1.0/src/rank/slimmer.rs +613 -0
  38. rustai-0.1.0/src/search/academic.rs +482 -0
  39. rustai-0.1.0/src/search/duckduckgo.rs +152 -0
  40. rustai-0.1.0/src/search/feeds.rs +460 -0
  41. rustai-0.1.0/src/search/mod.rs +442 -0
  42. rustai-0.1.0/src/search/searxng.rs +151 -0
  43. rustai-0.1.0/src/search/wikipedia.rs +140 -0
  44. rustai-0.1.0/src/text.rs +156 -0
  45. rustai-0.1.0/tests/conftest.py +19 -0
  46. rustai-0.1.0/tests/integration.rs +206 -0
  47. rustai-0.1.0/tests/test_extract.py +234 -0
  48. rustai-0.1.0/tests/test_network.py +255 -0
  49. rustai-0.1.0/tests/test_slim.py +110 -0
  50. rustai-0.1.0/tests/test_utils.py +182 -0
@@ -0,0 +1,32 @@
1
+ # Rust
2
+ /target/
3
+ **/*.rs.bk
4
+ *.pdb
5
+
6
+ # Python
7
+ __pycache__/
8
+ *.py[cod]
9
+ *.so
10
+ *.pyd
11
+ .venv/
12
+ venv/
13
+ build/
14
+ dist/
15
+ *.egg-info/
16
+ .pytest_cache/
17
+ .mypy_cache/
18
+ .ruff_cache/
19
+
20
+ # maturin
21
+ /wheels/
22
+ /rustai-*.tar.gz
23
+ /rustai-*.whl
24
+
25
+ # Editors and OS
26
+ .DS_Store
27
+ .idea/
28
+ .vscode/
29
+ *.swp
30
+ /rustai-report.txt
31
+ /rustai-logs/
32
+ /wheelhouse/
@@ -0,0 +1,162 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project uses
5
+ [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ### Fixed
10
+
11
+ - **Extraction stopped early on pages whose content root scores onto a
12
+ fragment.** The guard against a bad root asked whether it was *starved* —
13
+ under five percent of the document's text — which catches a root that
14
+ collapsed to nothing and misses every root that merely stopped early. That
15
+ is the common failure: a docs page split into sibling sections gives a root
16
+ holding one of them, comfortably above the threshold and comfortably wrong.
17
+ The container is now extracted too and the larger result kept, both having
18
+ been through the same denoiser. Measured over 34 cached pages: 20% more text
19
+ overall, nothing regressed, no page reclassified. `python.org`'s asyncio
20
+ reference goes from 19.5% of its text to 90.6%, `doc.rust-lang.org`'s `Vec`
21
+ from 48% to 87.5%. A gate skips the second walk when the root already covers
22
+ the container, which keeps throughput where it was (1,796 docs/s against
23
+ 1,820).
24
+ - The chosen content root is no longer re-judged by the boilerplate rules
25
+ before being walked. It arrives already decided, and an article container
26
+ that still holds the navigation it is about to drop reads as a link farm by
27
+ raw link density — rejecting it discarded the document to save the part that
28
+ was leaving anyway.
29
+
30
+
31
+ - **Data tables were being dropped, five different ways.** A table of national
32
+ GDP figures survived none of them, and each cause hid the next:
33
+ - `header` is a chrome token, and it matches inside `sticky-header-multi` —
34
+ the class Wikipedia puts on every sortable table — exactly as it does
35
+ inside `site-header`. Compounds scoped to a component (`sticky-`, `row-`,
36
+ `column-`, `table-`) are now stripped before the vocabulary sees them;
37
+ page-scoped ones still read as boilerplate.
38
+ - Markup weight counted attribute payload in full, so one serialised JSON
39
+ blob per element (334 KB of markup around 7.5 KB of text) made a data
40
+ table look like an ad slot. An attribute value now contributes at most 128
41
+ bytes.
42
+ - Link density and text-to-markup ratio ask whether a container reads like
43
+ prose. A data table answers no however good it is: cells hold a word, and
44
+ a header row that cites its sources is mostly links. Table elements are
45
+ exempt from both; the vocabulary still catches an ad wherever it sits.
46
+ - `is_data_table` rejected anything over 4,000 nodes, and a 223-row table
47
+ comes to 4,522. The fallback for a rejected table is to walk it as
48
+ ordinary blocks, which emits nothing at all — cells are too short to
49
+ survive as paragraphs — so a cap set near real tables deleted the output
50
+ rather than degrading it.
51
+ - Index detection read a linked cell as a listing entry, classifying data
52
+ tables as front pages. Anchors inside a table with header cells are no
53
+ longer counted; tables *without* header cells still are, because that is
54
+ how Hacker News lays out its front page.
55
+
56
+ ### Added
57
+
58
+ - **Linux wheel builds needed `libclang` and nobody said so.** BoringSSL's
59
+ binding crate build-depends on `bindgen` as well as `cmake`, so a container
60
+ without clang fails with "Unable to find libclang" — a message that names
61
+ neither the crate nor the dependency that wanted it. The release workflow now
62
+ installs it, and both Linux targets build on manylinux 2_28 because bindgen
63
+ needs libclang 9+, newer than the CentOS 7 image can offer.
64
+
65
+ ### Added
66
+
67
+ - **Scholarly providers**: `arxiv`, `openalex` and `crossref`, all keyless and
68
+ free. OpenAlex abstracts are stored as an inverted index and are reconstructed
69
+ into readable text; arXiv results point at abstract pages rather than PDFs.
70
+ `contact_email` opts into the OpenAlex/Crossref polite pool.
71
+ - **Index-page extraction.** A front page, archive or aggregator defeats
72
+ article extraction for the same reason navigation is boilerplate everywhere
73
+ else — except there the link density is the point. Such pages now return an
74
+ inventory instead: titles, URLs, standfirsts and section headings, on
75
+ `Article.links`, with `Article.kind` saying which you got. Detection measures
76
+ how much of the page's prose belongs to a link rather than whether article
77
+ extraction failed, so a listing whose cards carry summaries is still
78
+ recognised. 12 of 12 correct across real news front pages, aggregators,
79
+ encyclopaedia articles, papers, READMEs and specs; ~1% of extraction time,
80
+ and `index_mode="never"` disables it.
81
+ - `index:<url>` as a search provider: a site with no feed and no sitemap is
82
+ still collectable.
83
+ - `extract_many` in the Python API: batch denoising across the rayon pool with
84
+ the GIL released. ~4x a loop over `extract`, ~30x `trafilatura`.
85
+
86
+ - **Proxy rotation** (`proxies`): one client per proxy, rotated round-robin.
87
+ A TLS fingerprint says what a client is; an address says who it is, and
88
+ IP-reputation blocking is what fingerprinting cannot touch.
89
+ - **`Retry-After` is honoured** in place of the backoff curve, capped by
90
+ `max_retry_after` — past which a delay is a refusal, not a wait.
91
+ - **Cookie persistence** (`cookie_file`, `Client.save_cookies()`): clearance
92
+ cookies are the expensive part of getting past a bot wall, and throwing them
93
+ away at process exit means paying for them again.
94
+
95
+ ### Fixed
96
+
97
+ - **Extraction on pages that defeat the HTML parser.** `tl` does not insert the
98
+ implied end tags a browser would, so an unclosed `<p>`, `<span>`, `<li>` or
99
+ `<table>` can end up owning the rest of the document — and on one real
100
+ Wikipedia article it did, producing a single 54,000-token "paragraph"
101
+ containing the whole page. Four guards now hold, each of which is also just
102
+ correct on well-formed markup:
103
+ - an element is only inline if it carries no block-level content;
104
+ - a `<p>`, `<li>` or `<blockquote>` holding blocks is walked, not flattened;
105
+ - a `<table>` holding headings or sections is laid out, not tabulated;
106
+ - a node holding the majority of a document's text is never boilerplate,
107
+ whatever its class or `role` says.
108
+ - **Subtree size was misreported.** `text_ratio`, one of the three headline
109
+ denoising signals, read `tl`'s per-tag source slice — which collapses to the
110
+ opening tag alone when the parser cannot match a close, reporting 28 bytes for
111
+ an element holding 180 KB. Markup weight is now accumulated directly.
112
+ - `Unit.text` disagreed with `Unit.markdown` for tables — it carried the source
113
+ subtree's text rather than what was written — so a mis-nested table reported
114
+ 3,870 characters of content behind 53 tokens of output, and every measurement
115
+ downstream inherited the error.
116
+ - The boilerplate vocabulary missed plurals: `reference` did not match
117
+ `references`, so Wikipedia citation lists survived and ate context budgets.
118
+ High-precision terms are now conclusive at any size, since a reference list or
119
+ comment thread runs far past the length guard that keeps the rest honest.
120
+ - JS-gate detection used an absolute text threshold, so a 350 KB front page
121
+ carrying 510 characters of text — plainly an app shell — was never escalated
122
+ to the headless renderer. The test is now the text-to-markup ratio.
123
+ - HTML-level redirects (`<meta http-equiv="refresh">` and `location.replace`)
124
+ are now followed, bounded to two hops and gated on a zero delay and a small
125
+ body. Sites that canonicalise URLs in the browser previously extracted to
126
+ nothing — they returned a valid `200` whose body was a redirect stub.
127
+
128
+ ## [0.1.0] — 2026-09-04
129
+
130
+ First release.
131
+
132
+ ### Added
133
+
134
+ - **Search router** over free providers — DuckDuckGo, the Wikipedia API, any
135
+ SearXNG instance, RSS/Atom feeds and XML sitemaps — queried concurrently and
136
+ combined with reciprocal rank fusion. URLs are canonicalised before fusion, so
137
+ the same page found by three providers counts once and ranks higher for it.
138
+ - **Fetcher** with Chrome TLS/JA3 and HTTP/2 fingerprint emulation via `wreq`,
139
+ plus `robots.txt` enforcement, `Crawl-delay`, per-host pacing, a concurrency
140
+ ceiling, streamed body caps, exponential backoff, and charset detection that
141
+ handles legacy encodings such as EUC-KR.
142
+ - **Denoiser** combining structural priors, link density and text-to-HTML ratio
143
+ with a Readability-style content score, emitting clean Markdown units that
144
+ each carry their heading breadcrumb and token cost.
145
+ - **Context slimmer** — BM25 at unit granularity, an information-density score,
146
+ and greedy MMR selection with an overlap-coefficient redundancy penalty, under
147
+ a token budget that covers the rendered output rather than just the units.
148
+ - **Python bindings** (PyO3, `abi3-py39`): `Client`, `research`, `extract`,
149
+ `slim`, and helpers, all releasing the GIL for the duration of the call.
150
+ - Optional headless-Chrome fallback (`browser` feature) for JS-gated pages,
151
+ triggered only after a static fetch has demonstrably returned an empty shell.
152
+
153
+ ### Notes
154
+
155
+ - `reqwest-impersonate` and its successor `rquest` are both fully yanked on
156
+ crates.io; this project uses their maintained continuation, `wreq`.
157
+ - `tl`'s `simd` feature requires a nightly toolchain and is exposed as the
158
+ opt-in `nightly-simd` feature. Text scanning in this crate is SIMD-accelerated
159
+ on stable regardless, via `memchr`.
160
+
161
+ [Unreleased]: https://github.com/imhyensuk/rustai/compare/v0.1.0...HEAD
162
+ [0.1.0]: https://github.com/imhyensuk/rustai/releases/tag/v0.1.0
@@ -0,0 +1,58 @@
1
+ # Contributing
2
+
3
+ Thanks for taking a look. Bug reports and pull requests are both welcome.
4
+
5
+ ## Getting set up
6
+
7
+ ```bash
8
+ git clone https://github.com/imhyensuk/rustai
9
+ cd rustai
10
+ python -m venv .venv && source .venv/bin/activate
11
+ pip install maturin pytest
12
+ maturin develop --release
13
+ ```
14
+
15
+ ## The checks CI runs
16
+
17
+ ```bash
18
+ cargo fmt --all --check
19
+ cargo clippy --all-targets -- -D warnings
20
+ cargo test --all-targets
21
+ cargo test --doc
22
+ pytest
23
+ ```
24
+
25
+ Do not run `cargo clippy --all-features`: the `nightly-simd` feature enables
26
+ `tl`'s `portable_simd`, which does not build on stable. Lint each real
27
+ combination instead — that is what `.github/workflows/ci.yml` does.
28
+
29
+ The live-internet tests are off by default and are not part of CI:
30
+
31
+ ```bash
32
+ pytest --network
33
+ ```
34
+
35
+ ## Notes on the code
36
+
37
+ - **The denoiser earns its keep on real pages, not synthetic ones.** If you
38
+ change a heuristic in `src/denoise/`, add a test with markup from a page that
39
+ actually broke — a fixture in `tests/integration.rs` is the right home.
40
+ - **Every heuristic constant should be able to explain itself.** The thresholds
41
+ in `DenoiseConfig`, `SlimConfig` and `density.rs` all carry a comment saying
42
+ what they trade off. Please keep that up when you add one.
43
+ - **The token budget is a guarantee, not an estimate.** `rank::slim` renders,
44
+ measures, and trims until the output fits. If you change the renderer, keep
45
+ that loop correct.
46
+ - **Politeness defaults stay on.** `robots.txt`, per-host pacing and body caps
47
+ are the reason this library is safe to hand to someone. Making them
48
+ configurable is fine; making them default-off is not.
49
+
50
+ ## Benchmarks
51
+
52
+ ```bash
53
+ python benches/corpus.py /tmp/rustai-corpus
54
+ cargo run --release --example bench -- /tmp/rustai-corpus
55
+ python benches/benchmark.py
56
+ ```
57
+
58
+ If a change moves those numbers, say so in the pull request.