No description
Find a file
emfurst 7b2bb49e3c Art 0.1.0
Built from art-dev 78fb10b.
2026-09-29 20:52:52 -04:00
art Art 0.1.0 2026-09-29 20:52:52 -04:00
data Art 0.1.0 2026-09-29 20:52:52 -04:00
docs Art 0.1.0 2026-09-29 20:52:52 -04:00
modelfiles Art 0.1.0 2026-09-29 20:52:52 -04:00
prompts Art 0.1.0 2026-09-29 20:52:52 -04:00
tests Art 0.1.0 2026-09-29 20:52:52 -04:00
.gitignore Art 0.1.0 2026-09-29 20:52:52 -04:00
.python-version Art 0.1.0 2026-09-29 20:52:52 -04:00
config.yaml Art 0.1.0 2026-09-29 20:52:52 -04:00
LICENSE Art 0.1.0 2026-09-29 20:52:52 -04:00
pyproject.toml Art 0.1.0 2026-09-29 20:52:52 -04:00
README.md Art 0.1.0 2026-09-29 20:52:52 -04:00
uv.lock Art 0.1.0 2026-09-29 20:52:52 -04:00

Art

Art is a small, locally run LLM toolchain for rheology research. It accompanies the talk "Art: An LLM toolchain for rheology research" (Society of Rheology Annual Meeting, Boston, October 2026, session "AI and ML in Rheology"), and is meant as a starting point that other rheologists can read in one sitting, adopt, and adapt.

Everything runs on your own machine: embedding and re-ranking models are cached locally from HuggingFace, and text generation (coming in the next stage) goes to Ollama on localhost. No cloud APIs.

Art has two parts:

  1. bingham, a literature screener. It pulls new abstracts from the Journal of Rheology and arXiv, screens them against your interest profiles, and writes a daily Markdown digest of the papers worth reading. A Bingham plastic does not flow until the stress exceeds a yield threshold; most abstracts stay put, and only those that clear a calibrated threshold come through.
  2. Corpus search (planned), hybrid retrieval over a locally indexed collection of rheology papers, with raw-evidence inspection and grounded synthesis with citations.

Status

bingham fetches, screens, and writes digests. The LLM passes (a short summary, a topic label, and a one-sentence match reason for each highlighted paper) are next; until then, each digest entry shows the opening sentences of the abstract and the screening scores. Thresholds are provisional until calibrated on a hand-labeled set.

How bingham works

fetch -> store -> embed -> screen -> re-rank -> digest
(code)   (SQLite) (bge)    (cosine   (cross-    (code)
                            + BM25)   encoder)
  • Fetch. arXiv announcements from the per-category RSS feeds at rss.arxiv.org; J. Rheol. from Crossref (by ISSN) merged by DOI with the AIP current-issue feeds, which carry abstracts but no authors. Backfills and skipped days come from arXiv's OAI-PMH interface.
  • Store. One SQLite file holds papers, embeddings, scores, and a record of every run, so no stage is recomputed unless its model or profile has changed.
  • Screen. Each paper (title and abstract) is embedded once with BAAI/bge-large-en-v1.5. For each profile it gets a cosine similarity (the best match over the profile description and its exemplar papers) and a BM25 score against the profile's keywords, which catches abbreviations such as SAOS, LAOS, and DST and names that embeddings blur. The top candidates by either score, plus any paper by a tracked author, go on.
  • Re-rank. BAAI/bge-reranker-v2-m3 reads each (profile description, paper) pair together and scores it from 0 to 1. A paper is highlighted when its score clears the profile's threshold.
  • Digest. One Markdown file per day in digests/, grouped by profile and ordered by score, with a footer recording counts, models, host, device, and time per stage.

Retrieval models decide relevance; the LLM, when added, only writes about papers that have already passed the screen, and never produces identifiers (DOIs, arXiv IDs, titles, authors, and dates come straight from the feed metadata).

Getting started

Requirements

  • Python 3.12 and uv
  • About 3.5 GB of disk for the two HuggingFace models
  • A GPU is optional. Tested on an NVIDIA RTX 4090 (CUDA) under Linux and an Apple M3 Pro (MPS) under macOS; the device is chosen automatically (cuda, then mps, then cpu).
  • Ollama, for the LLM passes once they are added (not needed yet)

Install

git clone https://lem.che.udel.edu/git/furst/art.git
cd art
uv sync
uv run bingham download-models    # once, with network access

download-models fetches the embedder and re-ranker into ./models/. Every other command runs with HuggingFace in offline mode.

On Linux with an NVIDIA GPU, use a driver from the 580 series or later (CUDA 13). With older drivers, the standard PyTorch wheels cannot use the GPU, and Ollama 0.34 falls back to the CPU without an error (it still reports "100% GPU" in ollama ps, while generation slows to minutes per call). If you cannot update the driver, route torch to the CUDA 12.6 index in pyproject.toml ([tool.uv.sources], index https://download.pytorch.org/whl/cu126) and use an Ollama release that supports your driver.

First run

uv run bingham fetch                                  # J. Rheol. back to jor.backfill_from, plus today's arXiv
uv run bingham fetch --source arxiv --since 2026-09-21   # optional: a week of arXiv to screen
uv run bingham screen
uv run bingham digest --since 2026-09-21

The first fetch backfills J. Rheol. from Crossref (about 145 papers since January 2025). arXiv RSS is empty on Saturdays and Sundays, so a weekend first run fetches no arXiv papers unless you add --since. Backfilled papers stay out of the default daily digest; digest --since DATE includes them.

Running

uv run bingham daily                     # fetch + screen + digest; the usual command
uv run bingham fetch [--source arxiv|jor|all] [--since DATE] [--refresh]
uv run bingham screen
uv run bingham digest [--since DATE]
uv run bingham topic "shear thickening and frictional contacts" [-n 15]
  • daily writes digests/YYYY-MM-DD.md covering everything fetched since the previous digest. It is safe to run by hand at irregular intervals: if a weekday's arXiv announcement was missed, the next run fills the gap from OAI-PMH and says so in the digest footer. Gaps longer than arxiv.max_catchup_days are stored as backfill instead.
  • fetch --since DATE backfills arXiv (OAI-PMH) and J. Rheol. (Crossref) from a date. Requests to arXiv are spaced at least three seconds apart.
  • topic ranks every stored paper against a free-text query, with no profile and no LLM.
  • Raw feed responses are cached for feed_cache_ttl_hours (6); --refresh ignores the cache.

Measured run times

Machine Screening a week (about 850 papers, 8 profiles) A typical day
RTX 4090 workstation 37 s, including model loading a few seconds
M3 Pro laptop about 20 s per profile under a minute (estimated)

Adapting it to your own interests

Your research-specific files live in a user/ directory inside the checkout. It is ignored by this repository, so your profiles, the people you track, and your contact address never end up in the code. Art reads each file from user/ first and falls back to the generic versions in data/ (or the examples in data/examples/), and merges user/config.yaml over config.yaml.

mkdir user
cp data/examples/profiles.yaml data/examples/config.yaml data/examples/interest_profile.md user/
  • user/profiles.yaml: one entry per topic, each with a description (two or three sentences in the field's vocabulary, used for embedding and re-ranking), keywords for BM25, and optional exemplars (DOIs or arXiv IDs of papers that typify the topic; abstracts are fetched once, or can be pasted in). tracked_authors lists people whose papers are always shown. Editing a profile re-screens against that profile only.
  • user/config.yaml: your contact email (sent to arXiv and Crossref), arXiv categories, and per-profile thresholds. Anything in config.yaml can be overridden here; nested keys override and lists replace.
  • user/interest_profile.md: a prose version of your interests, used only by the LLM-as-classifier baseline.
  • data/vocabulary.md and data/feeds.yaml: the rheology term list that keywords are drawn from, and journal RSS feeds (Rheologica Acta, Soft Matter, JNNFM, and Macromolecules are listed but disabled). Copy either into user/ to customize it.

Re-ranker scores depend on how broad a profile's description is, so thresholds are set per profile. The digest applies the current thresholds, so changing one needs no re-screen.

Drafting a profile from your own papers. Instead of writing user/profiles.yaml from scratch, let bingham init draft it from papers you already have:

uv run bingham fetch                                   # first, so there is a background corpus to compare against
uv run bingham init --orcid 0000-0002-1825-0097        # your ORCID iD
uv run bingham init --ids my_papers.txt                # or one DOI or arXiv ID per line
uv run bingham init --csl zotero.json --bib refs.bib   # or Zotero exports (CSL JSON, BibTeX); sources can be combined

It fetches abstracts from Crossref and arXiv (cached in user/seed/), groups the papers into topics (--topics, default 5) by embedding similarity, picks each topic's most central papers as exemplars, suggests keywords from phrases that recur in the topic and from data/vocabulary.md, and suggests tracked authors from coauthors who appear on several of the papers. The LLM writes only each topic's name and description, as a draft. The results go to user/profiles.draft.yaml, user/interest_profile.draft.md, and user/vocabulary.draft.md (abbreviations defined in your papers but missing from the vocabulary); existing files are never overwritten. Papers too unlike the rest to form a topic are listed at the end of the draft rather than forced into one. With fewer than about 30 seed papers, expect to merge and edit topics by hand.

Then check what each profile would pick up from the stored papers, and choose thresholds:

uv run bingham profile-test --file user/profiles.draft.yaml

When you are satisfied, save the draft as user/profiles.yaml.

Using Art on more than one machine. Make user/ a small private git repository of its own and clone it into each checkout (git clone <your-private-repo> user). Sync with git pull and git push inside user/. The SQLite store, digests, and models stay local to each machine. To keep user/ elsewhere, set ART_USER_DIR.

Repository layout

config.yaml                 base settings (paths, arXiv, J. Rheol., screening, LLM)
data/examples/              example profiles, user config, and interest profile
data/vocabulary.md          rheology vocabulary
data/feeds.yaml             journal RSS feeds
user/                       your own files (not tracked; see above)
modelfiles/                 Ollama Modelfile for the researcher-tone model
docs/failure-modes.md       curated log of small-model failure modes
art/
  cli.py                    the bingham command
  env.py                    offline HuggingFace settings, config, device, host
  store.py                  SQLite schema and access
  feeds/fetch.py            arXiv RSS, OAI-PMH, and API; journal RSS; Crossref
  feeds/collect.py          daily and backfill fetch logic, arXiv catch-up
  feeds/cache.py            raw-fetch cache with a TTL
  profile/profiles.py       load and validate profiles.yaml
  screen/                   embed.py, bm25.py, rerank.py, screen.py
  digest.py                 Markdown digest
  cluster.py                average-linkage clustering (field pulse, profile drafts)
  toolbox/                  seeds.py (ORCID, ID lists, BibTeX, CSL JSON), draft.py (bingham init)
  summarize/summarize.py    LLM summary and topic label (being wired in)
tests/                      uv run pytest

Generated files (data/art.sqlite, data/cache/, digests/, models/) are not tracked.

Tests

uv run pytest

The tests cover the deterministic parts: feed parsing (against a saved arXiv RSS snapshot), merging papers from several sources, author matching, and the arXiv catch-up logic. They need no network and no models.

Art copies patterns, with attribution in the code, from:

  • llm-workshop, sections 02-ollama, 03-rag, and 04-semantic-search
  • ssearch, the hybrid retrieval stack (bge embeddings, bge re-ranker, BM25, offline model cache)
  • Stan (arXiv:2603.04657), for its taxonomy of small-model failure modes and its practice of splitting composite LLM tasks

License

MIT; see LICENSE. Copyright (c) 2026 Eric M. Furst.