6.0 KiB
Implementation Plan: RAG Retrieval Quality + Topic Summary Persistence
Branch: 004-rag-retrieval-quality | Date: 2026-04-07 | Spec: spec.md
Input: Feature specification from /specs/004-rag-retrieval-quality/spec.md + user request: "Save summaries by topic; list previously saved summaries; button to generate new."
Summary
Improve RAG retrieval quality by (1) expanding user queries via LLM rewrite to bridge vocabulary gaps and (2) validating generated citations against the retrieved context to eliminate hallucinated references. Additionally, persist generated topic summaries to the database so students can revisit past summaries and generate new ones on demand.
Technical Context
Language/Version: Java 21 (backend), TypeScript / Node 20 (frontend)
Primary Dependencies: Spring Boot 4.0.5, Spring AI 2.0.0-M4, OpenAI API (chat + embeddings), Vue 3.4, Pinia 2.1, Axios 1.7
Storage: PostgreSQL (JPA + Flyway), pgvector (VectorStore)
Testing: JUnit 5, Spring Boot Test, Vitest
Target Platform: Linux server
Project Type: Web application (backend API + Vue frontend)
Performance Goals: RAG query latency remains acceptable (one additional LLM call for query expansion, ~1–2s overhead)
Constraints: KISS — no new architectural layers; no new external services; single deployable backend + frontend
Constitution Check
| Principle | Status | Notes |
|---|---|---|
| I — KISS | ✅ Pass | Query expansion = one LLM call. Citation validation = string scan. Summary persistence = one new table + minimal service change. No new layers. |
| II — Easy to Change | ✅ Pass | QueryExpansionService, CitationValidatorService, TopicSummaryRepository are small, focused interfaces. Swappable without touching callers. |
| III — Web-First | ✅ Pass | All new functionality exposed via REST at /api/v1/.... Frontend-only UI changes. |
| IV — Documentation as Architecture | ⚠️ Action needed | README must be updated to reflect new topic_summary table and the summary history flow. Update in the same PR. |
Project Structure
Documentation (this feature)
specs/004-rag-retrieval-quality/
├── plan.md # This file
├── research.md # Phase 0 — decisions on query expansion, citation grounding, summary persistence
├── data-model.md # Phase 1 — topic_summary table, ExpandedQuery, LabelledContext DTOs
├── contracts/
│ ├── chat-api.md # Existing chat API (updated for labelled sources)
│ └── topics-api.md # New/updated topics API (summary list + detail endpoints)
└── tasks.md # Phase 2 output (/speckit.tasks command)
Source Code
backend/
├── src/main/java/com/aiteacher/
│ ├── retrieval/
│ │ ├── QueryExpansionService.java (NEW) — LLM rewrite of user query
│ │ ├── ExpandedQuery.java (NEW) — value object {original, rewritten}
│ │ ├── LabelledContext.java (NEW) — value object {sectionLabels, figureLabels, promptText}
│ │ └── CitationValidatorService.java (NEW) — strip unknown [Sx]/[Fx] refs from answer text
│ └── topic/
│ ├── TopicSummaryEntity.java (NEW) — JPA entity for topic_summary table
│ ├── TopicSummaryRepository.java (NEW) — findByTopicIdOrderBySummaryNumberAsc
│ ├── SavedSummaryItem.java (NEW) — DTO for list view (id, summaryNumber, generatedAt)
│ ├── TopicSummaryResponse.java (MODIFY) — add id: UUID, summaryNumber: int fields
│ ├── TopicSummaryService.java (MODIFY) — persist after generation; add list/get methods
│ └── TopicController.java (MODIFY) — add GET /summaries and GET /summaries/{id}
├── src/main/resources/db/migration/
│ └── V6__topic_summary.sql (NEW) — create topic_summary table
└── chat/
└── ChatService.java (MODIFY) — use QueryExpansionService + CitationValidatorService
frontend/
└── src/
├── stores/
│ └── topicStore.ts (MODIFY) — add fetchSummaries(), fetchSummaryDetail(), summaryList state
└── views/
└── TopicsView.vue (MODIFY) — show summary list when topic selected, "Generate New" button
Structure Decision: Option 2 (web application). Existing backend/ + frontend/ directories. No new top-level directories.
Complexity Tracking
No constitution violations introduced.
Phase 0: Research (complete)
See research.md. All decisions resolved:
- Query expansion: single LLM rewrite call
- Citation grounding: ref-label tagging + post-generation string scan
- Summary persistence: new
topic_summarytable, sequential numbering at insert time
Phase 1: Design (complete)
Data model
See data-model.md. New topic_summary table; two new in-memory value objects; TopicSummaryResponse extended with id + summaryNumber.
API contracts
POST /api/v1/topics/{id}/summary— now persists and returnsid+summaryNumberGET /api/v1/topics/{id}/summaries— list metadata (no full text)GET /api/v1/topics/{id}/summaries/{summaryId}— full detail
Frontend behaviour
When a topic card is clicked (before generating):
- Call
GET /api/v1/topics/{id}/summaries. - If summaries exist → show list panel with chips: "Summary #1 · Apr 6", "Summary #2 · Apr 7", … + a "Generate New" button.
- If no summaries → show "Generate Summary" button (current behaviour).
- Clicking a chip → call
GET /api/v1/topics/{id}/summaries/{summaryId}→ display the saved summary. - Clicking "Generate New" → call
POST /api/v1/topics/{id}/summary→ display new summary, refresh list.