Grounded refuse
RAG's real value is refusing what the documents don't support. The corpus is a
Showcase — Grounded refuse
RAG's real value is refusing what the documents don't support. The corpus is a narrow HR policy. Ask about PTO, sick leave, remote work, or expenses and it answers with a citation; ask about the stock price or the weather and it replies "That isn't covered in the HR policies I have" instead of improvising. The refusal is the guardrail against confident hallucination.
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 narrow policy KB and a system prompt with a hard refusal rule.frontend/app/page.tsx— question box; try in-scope and out-of-scope questions.
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 grounded-refuse.zip
cd grounded-refuse
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 2 (OpenAI): grounded answers that refuse when unsupported.
RAG's real value isn't answering — it's REFUSING to answer what the documents
don't support. The corpus here is a narrow HR policy. Ask about leave or
expenses and it answers with a citation; ask about anything else — the stock
price, the weather, last night's game — and it declines instead of improvising.
That refusal is the guardrail against confident hallucination.
"""
from openai import OpenAI
from store import VectorStore
_client = OpenAI()
_EMBED_MODEL = "text-embedding-3-small"
_CHAT_MODEL = "gpt-5.4-nano"
_KB = [
{"id": "pto", "text": "Full-time employees accrue 15 days of paid time off per year, rolling over up to 5 days."},
{"id": "sick", "text": "Sick leave is separate from PTO: 8 paid sick days per year, no rollover."},
{"id": "remote", "text": "Employees may work remotely up to 3 days per week with manager approval."},
{"id": "expenses", "text": "Reimbursable expenses must be submitted within 30 days with a receipt via the Expenses portal."},
{"id": "parental", "text": "Parental leave is 12 weeks paid, available to all parents within a year of the birth or adoption."},
]
_SYSTEM = (
"You are an HR policy assistant. Answer strictly from the policy context "
"below and cite the [id] you used. If the answer is not in the context, "
"reply exactly: 'That isn't covered in the HR policies I have.' Do not guess "
"and do not use outside knowledge.\n\nContext:\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(question: str) -> str:
hits = _get_store().search(question.strip(), k=3)
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": question.strip()}],
)
return response.output_text
backend/ai_gemini.py
"""Showcase 2 (Gemini): grounded answers that refuse when unsupported.
Same narrow policy corpus and the same hard refusal rule, on 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": "pto", "text": "Full-time employees accrue 15 days of paid time off per year, rolling over up to 5 days."},
{"id": "sick", "text": "Sick leave is separate from PTO: 8 paid sick days per year, no rollover."},
{"id": "remote", "text": "Employees may work remotely up to 3 days per week with manager approval."},
{"id": "expenses", "text": "Reimbursable expenses must be submitted within 30 days with a receipt via the Expenses portal."},
{"id": "parental", "text": "Parental leave is 12 weeks paid, available to all parents within a year of the birth or adoption."},
]
_SYSTEM = (
"You are an HR policy assistant. Answer strictly from the policy context "
"below and cite the [id] you used. If the answer is not in the context, "
"reply exactly: 'That isn't covered in the HR policies I have.' Do not guess "
"and do not use outside knowledge.\n\nContext:\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(question: str) -> str:
hits = _get_store().search(question.strip(), k=3)
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=question.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