Q&A over docs
Chat with your own document: long context (wk13) + grounding (wk9) + a moderation check (wk16). Paste a document, put your question on a `Q:` line, and get an answer grounded in the text.
Showcase — Q&A over docs
Chat with your own document: long context (wk13) + grounding (wk9) + a moderation check (wk16). Paste a document, put your question on a Q: line, and get an answer grounded in the text.
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/ai_openai.py/backend/ai_gemini.py— parse doc + question, moderate, grounded answer.frontend/app/page.tsx— document + question box, grounded answer.
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 qa-over-docs.zip
cd qa-over-docs
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): grounded Q&A over your own document.
Paste a document and a question (on a 'Q:' line) and get an answer grounded in
the text, with the question moderated first. Long context (week 13) plus
grounding (week 9) plus a safety check (week 16) — the everyday "chat with a
document" feature, built from parts you now know cold.
"""
from openai import OpenAI
_client = OpenAI()
_MODEL = "gpt-5.4-nano"
def run(text: str) -> str:
body = text.strip()
if "Q:" in body:
idx = body.rfind("Q:")
document, question = body[:idx].strip(), body[idx + 2:].strip()
else:
document, question = body, "Summarize this document."
if not document:
return "Paste a document, then put your question on a line starting with 'Q:'."
if _client.moderations.create(model="omni-moderation-latest", input=question).results[0].flagged:
return "[question blocked by moderation]"
response = _client.responses.create(
model=_MODEL,
instructions=f"Answer the question using ONLY this document. Quote the relevant part. If it "
f"isn't covered, say so.\n\nDOCUMENT:\n{document}",
input=[{"role": "user", "content": question}],
)
return response.output_text
backend/ai_gemini.py
"""Showcase 3 (Gemini): grounded Q&A over your own document.
Same long-context + grounding + moderation flow, on Gemini.
"""
import os
from google import genai
from google.genai import types
_client = genai.Client(api_key=os.environ["GEMINI_API_KEY"])
_MODEL = "gemini-3.1-flash-lite"
def _flagged(text: str) -> bool:
r = _client.models.generate_content(
model=_MODEL, contents=[types.Content(role="user", parts=[types.Part(text=text)])],
config=types.GenerateContentConfig(system_instruction=(
"Reply 'flag' if this text is hateful, harassing, sexual, violent, or dangerous; else 'ok'. One word.")),
)
return "flag" in (r.text or "").lower()
def run(text: str) -> str:
body = text.strip()
if "Q:" in body:
idx = body.rfind("Q:")
document, question = body[:idx].strip(), body[idx + 2:].strip()
else:
document, question = body, "Summarize this document."
if not document:
return "Paste a document, then put your question on a line starting with 'Q:'."
if _flagged(question):
return "[question blocked by moderation]"
response = _client.models.generate_content(
model=_MODEL,
contents=[types.Content(role="user", parts=[types.Part(text=question)])],
config=types.GenerateContentConfig(
system_instruction=f"Answer the question using ONLY this document. Quote the relevant part. "
f"If it isn't covered, say so.\n\nDOCUMENT:\n{document}",
),
)
return response.text or ""
Project files
.gitignoreREADME.es.mdREADME.mdbackend/Dockerfilebackend/ai_gemini.pybackend/ai_openai.pybackend/main.pybackend/requirements.txtbootstrap-secrets.shdocker-compose.ymlfrontend/Dockerfilefrontend/app/layout.tsxfrontend/app/page.tsxfrontend/next.config.tsfrontend/package.jsonfrontend/tsconfig.json