Course EN
← back to chapter

Preguntas y respuestas sobre documentos

Chatea con tu propio documento: long context (sem. 13) + grounding (sem. 9) + una revisión de moderación (sem. 16). Pega un documento, pon tu pregunta en una línea `Q:`, y recibe una respuesta anclada en el texto.

Showcase — Preguntas y respuestas sobre documentos

Chatea con tu propio documento: long context (sem. 13) + grounding (sem. 9) + una revisión de moderación (sem. 16). Pega un documento, pon tu pregunta en una línea Q:, y recibe una respuesta anclada en el texto.

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/ai_openai.py / backend/ai_gemini.py — parsea doc + pregunta, modera, respuesta con grounding.
  • frontend/app/page.tsx — caja de documento + pregunta, respuesta con grounding.

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.

Descargar qa-over-docs.zip

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

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 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 ""

Archivos del proyecto

  • .gitignore
  • README.es.md
  • README.md
  • backend/Dockerfile
  • backend/ai_gemini.py
  • backend/ai_openai.py
  • backend/main.py
  • backend/requirements.txt
  • bootstrap-secrets.sh
  • docker-compose.yml
  • frontend/Dockerfile
  • frontend/app/layout.tsx
  • frontend/app/page.tsx
  • frontend/next.config.ts
  • frontend/package.json
  • frontend/tsconfig.json