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

1 star

Watchers

0 watching

Forks

Packages

 
 
 

Contributors