Q&A de documentos
El loop canónico de RAG. Tu pregunta recupera los mejores chunks de los docs de
Showcase — Q&A de documentos
El loop canónico de RAG. Tu pregunta recupera los mejores chunks de los docs de
un producto; el modelo responde solo a partir de ellos, cita los [id] que usó,
y los ids de los chunks recuperados se muestran de vuelta para que puedas
verificar las citas contra lo que se trajo. Pregunta "does the free plan include
automations?" y responde a partir de los chunks de plans y automations.
Córrelo
bash bootstrap-secrets.sh # reads ../../../../.env, writes secrets/
docker compose up --build # default: PROVIDER=openai
Abre http://localhost:3000. Gemini: PROVIDER=gemini docker compose up --build.
Qué hay aquí
backend/store.py— elVectorStorecompartido (retrieval).backend/ai_openai.py/backend/ai_gemini.py— la KB, el retrieval, el prompt con fundamento y la llamada de generación.frontend/app/page.tsx— caja de pregunta + respuesta con citas.
Detenlo
docker compose down
Ejecútalo en tu máquina
Descarga el proyecto como ZIP y córrelo con Docker. Levanta un backend FastAPI y un frontend Next.js en localhost:3000.
unzip doc-qa.zip
cd doc-qa
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
Escribe algo, elige un proveedor y ejecuta el mismo código de Código contra la API real. Requiere iniciar sesión.
Los mismos módulos que ejecuta el botón Run. El proyecto completo (frontend, Dockerfile, compose) está en el ZIP, pestaña README.
backend/ai_openai.py
"""Showcase 1 (OpenAI): document Q&A — the canonical RAG loop.
Retrieve the top chunks for a question, paste them in as numbered context, and
make the model answer only from them, citing the [id]s it used. The retrieved
ids are echoed at the bottom so you can check the citations against what was
actually fetched.
"""
from openai import OpenAI
from store import VectorStore
_client = OpenAI()
_EMBED_MODEL = "text-embedding-3-small"
_CHAT_MODEL = "gpt-5.4-nano"
_KB = [
{"id": "plans", "text": "TaskFlow Free includes 3 boards. Pro is $10/month for unlimited boards and Gantt charts."},
{"id": "invite", "text": "Invite members from Team > Invite; they join as Editors and can be changed to Admin."},
{"id": "automations", "text": "Automations (e.g. 'when a card moves to Done, notify the owner') are a Pro feature."},
{"id": "export", "text": "Export any board to CSV from Board > Export. Attachments are not included in exports."},
{"id": "api", "text": "The API is available on Pro, rate-limited to 120 requests/minute, keyed under Settings > API."},
{"id": "mobile", "text": "The mobile app supports viewing and moving cards; automations must be edited on the web."},
]
_SYSTEM = (
"You are TaskFlow's support assistant. Answer using ONLY the context below. "
"Cite the sources you use by their [id]. If the context does not contain the "
"answer, say 'I don't have that in the docs.' Never use outside knowledge.\n\n"
"Context:\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()}],
)
used = ", ".join(item["id"] for _, item in hits)
return f"{response.output_text}\n\n— retrieved chunks: {used}"
backend/ai_gemini.py
"""Showcase 1 (Gemini): document Q&A — the canonical RAG loop.
Same retrieval, same grounding rules, Gemini's embedding + chat models.
"""
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": "plans", "text": "TaskFlow Free includes 3 boards. Pro is $10/month for unlimited boards and Gantt charts."},
{"id": "invite", "text": "Invite members from Team > Invite; they join as Editors and can be changed to Admin."},
{"id": "automations", "text": "Automations (e.g. 'when a card moves to Done, notify the owner') are a Pro feature."},
{"id": "export", "text": "Export any board to CSV from Board > Export. Attachments are not included in exports."},
{"id": "api", "text": "The API is available on Pro, rate-limited to 120 requests/minute, keyed under Settings > API."},
{"id": "mobile", "text": "The mobile app supports viewing and moving cards; automations must be edited on the web."},
]
_SYSTEM = (
"You are TaskFlow's support assistant. Answer using ONLY the context below. "
"Cite the sources you use by their [id]. If the context does not contain the "
"answer, say 'I don't have that in the docs.' Never use outside knowledge.\n\n"
"Context:\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)),
)
used = ", ".join(item["id"] for _, item in hits)
return f"{response.text or ''}\n\n— retrieved chunks: {used}"
Archivos del proyecto
.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