This document describes the implemented architecture of RAGU.
It is intended for:
- maintainers
- contributors
- AI agents preparing non-trivial changes
For setup and runtime usage, see README.md. For contributor guidance, see CONTRIBUTING.md. For operations, see OPERATIONS.md.
RAGU is a local-first, Streamlit-based retrieval-augmented generation application for structural biology PDFs.
The architecture has two major halves:
- application/runtime logic in
app.py - Docker Compose services that provide inference, vector storage, and observability
flowchart LR
User[User]
App[Streamlit app]
PDFs[Uploaded PDFs]
Ollama[Ollama]
Chroma[Chroma]
Reranker[Cross-encoder]
MolStar[Mol* viewer]
Piper[Piper]
User --> App
PDFs --> App
App --> Chroma
App --> Ollama
App --> Reranker
App --> MolStar
App --> Piper
flowchart TD
Upload[Upload PDFs] --> Load[PyMuPDFLoader]
Load --> Split[RecursiveCharacterTextSplitter]
Split --> Embed[Ollama embeddings]
Embed --> Store[Chroma collection]
Question[User question] --> Retrieve[Retrieve candidate chunks]
Retrieve --> Rerank[Rerank with CrossEncoder]
Rerank --> Prompt[Build context prompt]
Prompt --> Generate[Ollama chat generation]
Generate --> Render[Streamlit streamed answer]
Render --> PDB[Search PDB code]
PDB --> Viewer[Mol* structure viewer]
Render --> TTS[Piper synthesis if available]
The implemented code is still mostly monolithic.
Current practical boundary:
app.pycontains UI orchestration, service clients, document ingestion, retrieval, reranking, generation, PDB extraction, and TTS logic
This is important:
- the architecture is operationally split across services
- the Python code is not yet structurally split into clean modules
Why:
- fast iteration
- simple local UX
- easy to deploy as a single Python process
Why:
- local-first inference
- self-hosted model control
- simple deployment story
Why:
- avoids embedded SQLite permission issues in the app container
- better separation between application and vector-store runtime
Why:
- improves retrieval quality with a small, understandable step
Why:
- audio output is useful but not essential
Why:
- Chroma has a documented OTEL path
- Ollama monitoring is best handled through an exporter + Prometheus stack
process_documentget_ollama_clientget_chroma_clientget_vector_collectionadd_to_vector_collectionquery_collectionre_rank_cross_encoderscall_llmsearch_pdb_codeget_piper_statussynthesize_speech
The app uses Streamlit resource caching for long-lived expensive objects such as:
- Ollama client
- Chroma client/collection
- cross-encoder reranker
flowchart LR
App[app]
Ollama[ollama]
Chroma[chroma]
Redis[redis]
OTel[otel-collector]
Zipkin[zipkin]
Exporter[ollama-exporter]
Prometheus[prometheus]
Grafana[grafana]
CAdvisor[cadvisor]
App --> Ollama
App --> Chroma
App -. future .-> Redis
Chroma --> OTel
OTel --> Zipkin
OTel --> Prometheus
Ollama --> Exporter
Exporter --> Prometheus
CAdvisor --> Prometheus
Prometheus --> Grafana
- uploaded PDF enters the Streamlit app
- file is temporarily written
- PyMuPDF extracts text
- chunks are generated
- embeddings are requested from Ollama
- chunk vectors are stored in Chroma
- user asks a question
- relevant chunks are retrieved from Chroma
- candidate chunks are reranked locally
- best context is sent to Ollama chat generation
- streamed answer is rendered in Streamlit
- generated answer text is scanned for a PDB identifier
- if a valid code is found, Mol* is embedded in the page
- structure data is loaded from the remote RCSB asset URL
- answer text is checked against Piper availability
- if Piper is available, synthesis runs inside the app container
- resulting audio is played in the browser
Owns:
- Streamlit UI
- document ingestion
- retrieval orchestration
- reranking
- chat request construction
- PDB detection
- TTS availability and playback preparation
Owns:
- model serving
- chat inference
- embedding inference
- model storage
Owns:
- vector persistence
- collection management
- retrieval API
Currently reserved for:
- future cache or session features
Own:
- trace collection and visualization
- metrics scraping
- dashboards
app.pyis still too large- UI code and runtime logic are mixed
- no automated tests yet protect the core helpers
- Redis exists in the topology but not in the feature set
The next healthy structural change is not a rewrite.
It is:
- extract non-UI helpers into focused modules
- keep
app.pyas the Streamlit orchestration layer - preserve env-driven runtime behavior
- preserve current Docker service boundaries
Before making non-trivial architectural changes:
- Read AGENT.md.
- Read README.md.
- Read this file.
- Read OPERATIONS.md if your change affects runtime behavior.
- Update related docs in the same change if service topology, env vars, or operational behavior changes.