Skip to content

fairdataihub/poster-bot

Repository files navigation

poster-bot

Semantic search + grounded chat over the posters.science corpus — 31,363 machine-actionable scientific posters (Zenodo + Figshare, DataCite 4.7 schema, which adds the native Poster resource type). Built for demo at BOSC 2026 / CollaborationFest.

Retrieval is pgvector HNSW over Alibaba-NLP/gte-large-en-v1.5 embeddings (1024-dim, one vector per poster) fused with Postgres full-text search; answers are generated by a small local LLM (ollama) grounded in the retrieved posters with inline DOI citations. No query ever leaves the host.

Deployments

Where How Guide
Laptop / any Docker host docker compose — one command, GPU-optional, fits an 8GB-VRAM laptop LOCAL-DEPLOY.md
Build host (GPU workstation) native pipeline (flatten → embed → load → index) + systemd services Quickstart below
Public URL (conference) relay VPS + outbound reverse-SSH tunnel, no home ports opened deploy/EXPOSE-via-relay-vps.md

The database itself (~185MB dump) is transferred out of band — too large for git. Produce it on a build host with scripts/dump.sh, load it on a target with scripts/restore.sh.

Architecture

poster *_complete.json ──> flatten_posters.py ──> embed_posters.py (GTE, fp32, GPU)
                                                        │  .npz shards
                                                        v
                        pgvector/pgvector:pg15 <── load_posters.py
                        (HNSW halfvec, m=16,       + init/03_indexes.sql
                         ef_construction=128)
                                │ read-only role
                                v
   static chat UI <── FastAPI app.py ──> ollama (small instruct model, e.g. qwen3:4b)
                      /api/search  /api/chat (SSE)

Everything binds to 127.0.0.1; public exposure is a separate reverse-proxy layer.

Components

Path Purpose
up_db.sh / down_db.sh launch/remove the pgvector container (plain docker run)
init/02_schema.sql posters table (metadata + vector(1024) + generated tsvector)
init/03_indexes.sql post-load HNSW/GIN/btree index build
app/flatten_posters.py canonical corpus → sanitized JSONL; license classification via the corpus repo's license_policy.py
app/embed_posters.py resumable GPU embedding into .npz shards (VRAM-capped, device-gated)
app/load_posters.py idempotent bulk upsert into Postgres
app/app.py FastAPI: hybrid RRF retrieval, SSE chat, per-session rate limits
app/static/index.html dependency-free chat UI (escape-by-default rendering)
deploy/*.service systemd --user units for the ollama instance and the API

Quickstart

cp .env.example .env && $EDITOR .env && chmod 600 .env
./up_db.sh                                   # pgvector container on 127.0.0.1:$POSTERBOT_DB_PORT
# create roles once, then apply schema (see init/02_schema.sql header)
python app/flatten_posters.py --out scratch/posters.jsonl
python app/embed_posters.py   --in  scratch/posters.jsonl
python app/load_posters.py
docker exec -i posterbot-db psql -U posterbot_owner -d posters < init/03_indexes.sql
systemctl --user enable --now posterbot-ollama posterbot-api
open http://127.0.0.1:8722

Python deps (Python 3.12): torch, sentence-transformers==3.4.1, transformers==4.57.1, psycopg2-binary, fastapi, uvicorn, httpx.

Licensing stance

Poster full text is served to the model/UI only for records whose rightsList classifies as allowed under the corpus license policy (CC-BY family, CC0, permissive software licenses, …). Everything else — including upstream _license_blocked records, whose extracted content is already stripped — contributes catalog metadata only (title, abstract, authors, DOI link). Poster text is OCR-derived and treated as untrusted data end-to-end: the LLM has no tools, citation links are constructed server-side from the database, and the UI renders exclusively via textContent.

Versioning

Semantic versioning. See CHANGELOG.md.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages