Cited summary
RAG beyond question answering. Give it a topic and it retrieves the relevant
Showcase — Cited summary
RAG beyond question answering. Give it a topic and it retrieves the relevant
research notes and synthesizes a short summary that pulls from several of them,
citing every claim with its [id]. Try "caffeine", "exercise", or "morning
light" and watch it weave the matching notes together — with sources.
Run
bash bootstrap-secrets.sh # reads ../../../../.env, writes secrets/
docker compose up --build # default: PROVIDER=openai
Open http://localhost:3000. Gemini: PROVIDER=gemini docker compose up --build.
What's where
backend/store.py— the sharedVectorStore.backend/ai_openai.py/backend/ai_gemini.py— the research notes, top-k retrieval, and a summarize-with-citations prompt.frontend/app/page.tsx— topic box + cited summary.
Stop
docker compose down
Run locally
Download the project as a ZIP and run it with Docker. Brings up a FastAPI backend + Next.js frontend on localhost:3000.
unzip cited-summary.zip
cd cited-summary
bash bootstrap-secrets.sh # one-time: pulls API keys into ./secrets
docker compose up --build # default provider: openai
# or: PROVIDER=gemini docker compose up --build
Type some input, pick a provider, and run the same code shown in Source against the live API. Sign-in required.
The same modules the Run button hits. The whole project (frontend, Dockerfile, compose) is in the ZIP under README.
backend/ai_openai.py
"""Showcase 3 (OpenAI): cited multi-passage summaries.
RAG isn't only question answering. Give it a topic, retrieve the several
passages that touch it, and have the model synthesize a short summary that
pulls from all of them — with [id] citations so every claim is traceable. The
corpus is a set of short research notes; ask for "sleep" or "caffeine" and it
weaves the relevant notes together.
"""
from openai import OpenAI
from store import VectorStore
_client = OpenAI()
_EMBED_MODEL = "text-embedding-3-small"
_CHAT_MODEL = "gpt-5.4-nano"
_KB = [
{"id": "n1", "text": "Adults who sleep 7-9 hours report better mood and sharper attention than short sleepers."},
{"id": "n2", "text": "Caffeine has a half-life of about 5 hours, so an afternoon coffee can disrupt night sleep."},
{"id": "n3", "text": "A consistent sleep and wake time strengthens the circadian rhythm more than total hours alone."},
{"id": "n4", "text": "Bright morning light exposure advances the body clock and helps you fall asleep earlier."},
{"id": "n5", "text": "Moderate exercise improves sleep quality, but vigorous exercise close to bedtime can delay it."},
{"id": "n6", "text": "Alcohol shortens the time to fall asleep but fragments REM sleep later in the night."},
]
_SYSTEM = (
"You are a research assistant. Using ONLY the notes below, write a short "
"(2-4 sentence) summary answering the user's topic, and cite every claim "
"with its [id]. If the notes don't cover the topic, say so.\n\nNotes:\n{context}"
)
_store: VectorStore | None = None
def _embed(texts):
return [item.embedding for item in _client.embeddings.create(model=_EMBED_MODEL, input=texts).data]
def _get_store() -> VectorStore:
global _store
if _store is None:
_store = VectorStore(_embed)
_store.add(_KB)
return _store
def run(topic: str) -> str:
hits = _get_store().search(topic.strip(), k=4)
context = "\n".join(f"[{item['id']}] {item['text']}" for _, item in hits)
response = _client.responses.create(
model=_CHAT_MODEL,
instructions=_SYSTEM.format(context=context),
input=[{"role": "user", "content": f"Summarize what the notes say about: {topic.strip()}"}],
)
return response.output_text
backend/ai_gemini.py
"""Showcase 3 (Gemini): cited multi-passage summaries.
Same research notes and citation rule, synthesized by Gemini.
"""
import os
from google import genai
from google.genai import types
from store import VectorStore
_client = genai.Client(api_key=os.environ["GEMINI_API_KEY"])
_EMBED_MODEL = "gemini-embedding-001"
_CHAT_MODEL = "gemini-3.1-flash-lite"
_KB = [
{"id": "n1", "text": "Adults who sleep 7-9 hours report better mood and sharper attention than short sleepers."},
{"id": "n2", "text": "Caffeine has a half-life of about 5 hours, so an afternoon coffee can disrupt night sleep."},
{"id": "n3", "text": "A consistent sleep and wake time strengthens the circadian rhythm more than total hours alone."},
{"id": "n4", "text": "Bright morning light exposure advances the body clock and helps you fall asleep earlier."},
{"id": "n5", "text": "Moderate exercise improves sleep quality, but vigorous exercise close to bedtime can delay it."},
{"id": "n6", "text": "Alcohol shortens the time to fall asleep but fragments REM sleep later in the night."},
]
_SYSTEM = (
"You are a research assistant. Using ONLY the notes below, write a short "
"(2-4 sentence) summary answering the user's topic, and cite every claim "
"with its [id]. If the notes don't cover the topic, say so.\n\nNotes:\n{context}"
)
_store: VectorStore | None = None
def _embed(texts):
return [e.values for e in _client.models.embed_content(model=_EMBED_MODEL, contents=texts).embeddings]
def _get_store() -> VectorStore:
global _store
if _store is None:
_store = VectorStore(_embed)
_store.add(_KB)
return _store
def run(topic: str) -> str:
hits = _get_store().search(topic.strip(), k=4)
context = "\n".join(f"[{item['id']}] {item['text']}" for _, item in hits)
response = _client.models.generate_content(
model=_CHAT_MODEL,
contents=[types.Content(role="user", parts=[types.Part(text=f"Summarize what the notes say about: {topic.strip()}")])],
config=types.GenerateContentConfig(system_instruction=_SYSTEM.format(context=context)),
)
return response.text or ""
Project files
.gitignoreREADME.es.mdREADME.mdbackend/Dockerfilebackend/ai_gemini.pybackend/ai_openai.pybackend/main.pybackend/requirements.txtbackend/store.pybootstrap-secrets.shdocker-compose.ymlfrontend/Dockerfilefrontend/app/layout.tsxfrontend/app/page.tsxfrontend/next.config.tsfrontend/package.jsonfrontend/tsconfig.json