PhyRAG.
Physics, grounded in the textbook ↘A local physics tutor that retrieves before it reasons.
An offline retrieval-augmented generation system for Class 9–10 physics. PhyRAG prepares a textbook knowledge base, merges two semantic retrieval paths, re-ranks evidence, and passes selected context to a locally hosted language model. A Next.js client streams answers from FastAPI.
The problem, and the approach.
What needed solving?
A general language model can confidently answer physics questions without having seen the school curriculum. For learners, unsupported explanations and missing formula context can be as harmful as no answer at all. The challenge was to make responses textbook-oriented without sending private learning activity to a hosted model.
What was built?
Keep the book as an explicit retrieval source; combine complementary dense retrieval candidates, prioritize passages with a cross-encoder, and use Gemma 2 through Ollama to produce answers. Question reformulation helps contextual follow-ups, while RAGChecker provides category-specific error evidence.
Architecture, without the noise.
This is a simplified conceptual diagram of the components documented in the repository; it is not a claim that all operations use a single synchronous pipeline.
Extract text; 900-character chunks and 100-character overlap.
FAISS / all-mpnet-base-v2 plus all-MiniLM-L6-v2 dense retrieval.
Merge candidates, cross-encoder score, remove weak context.
Adaptive prompts and chat history → Ollama gemma2:2b.
FastAPI SSE endpoint → Next.js per-session chat.
How it works, step by step.
- 01
Index the physics book
Extract the NCTB Class 9–10 physics text, split with RecursiveCharacterTextSplitter (900 / 100), embed and write a FAISS index.
server/main.py - 02
Handle conversational context
For follow-ups, reformulate the question into a standalone retrieval query before searching.
server/main.py - 03
Retrieve from two perspectives
Collect FAISS matches (k=5) and separate Sentence Transformer matches (top_k=3), then merge their candidates.
server/main.py - 04
Re-rank and filter
Score candidate passages with cross-encoder/ms-marco-MiniLM-L-6-v2 and apply the configured relevance threshold (0.5).
server/main.py - 05
Generate and stream
Select a factual, mathematical, reasoning, or general prompt, call the local model, and stream the output through /stream_response.
server/main.py
The choices behind the system.
Two retrieval paths rather than one
FAISS and a second dense Sentence Transformer retriever are combined before cross-encoder ranking. This improves candidate coverage opportunities; it does not guarantee complete retrieval.
Run inference without a paid LLM API
The generation endpoint targets Ollama gemma2:2b on the local machine. Model weights and embedding models may need an initial download.
Session context belongs to the service
The browser uses a UUID and localStorage while the backend retains per-session history in memory. That history is not durable after a server restart.
Inside the implementation.
Retrieval recipe
Semantic index
all-mpnet-base-v2 embeddings are used with FAISS for stored textbook chunks.
Second retriever
all-MiniLM-L6-v2 provides another retrieval route; candidates are merged rather than relying on a single ranked list.
Cross-encoder
ms-marco-MiniLM-L-6-v2 scores query–passage relevance before only selected context reaches the generation prompt.
Conversation routing
Query rewriting and adaptive prompts distinguish long-term textbook evidence from temporary dialogue context.
Local inference configuration
Configuration example documented in the public README. This is a small genuine configuration excerpt, not an independently executed benchmark.
llm = ChatOllama(model="gemma2:2b")
embedding_model = SentenceTransformer(
"all-MiniLM-L6-v2"
)
cross_encoder = CrossEncoder(
"cross-encoder/ms-marco-MiniLM-L-6-v2"
)SOURCE REFERENCE / README.md · Model ConfigurationResults in context.
Measured strengths and failures
RAGChecker scores below are reported in the repository for approximately 4,000 textbook-derived examples. They are category-specific internal evaluations, not external benchmark scores. A separate manual exercise involved 12 students.
| RAGChecker metric | Simple | Complex | Conversation | Math |
|---|---|---|---|---|
| F1 | 0.48 | 0.42 | 0.26 | 0.86 |
| Faithfulness | 0.53 | 0.47 | 0.42 | 0.82 |
| Recall | 0.71 | 0.67 | 0.58 | 0.92 |
| Precision | 0.43 | 0.37 | 0.33 | 0.81 |
Numbers here are reported by the project author in the linked public repository or CV. They are not independent tests performed for this portfolio.
Trade-offs & open questions.
Incomplete answers
In the repository failure analysis, a question about Newton’s three laws omitted the first law because retrieval returned insufficient evidence.
Physics reasoning mistakes
An inelastic-collision answer incorrectly preserved kinetic energy; the project records this as a failure case.
Long chats degrade
The README attributes poorer conversational F1 (0.26) to accumulated context, retrieval issues and resource limits.
Curriculum-limited coverage
One textbook is not a general physics corpus, and the project does not claim multilingual or university-level mastery.
Source directory.
These source paths are identified in the project implementation or accompanying documentation. Links open the corresponding GitHub source location; availability can change as repositories evolve.
Model orchestration, retrieval, prompt flow and routes
server/main.pyEnvironment-specific paths and indexes
server/config.pyNext.js chat, SSE consumption and sessions
clientRAGChecker table, manual study and failure cases
README.md