Course EN
← back to chapter

Resumen con citas

RAG más allá de las preguntas y respuestas. Dale un tema y recupera las notas de

Showcase — Resumen con citas

RAG más allá de las preguntas y respuestas. Dale un tema y recupera las notas de investigación relevantes y sintetiza un resumen corto que toma de varias de ellas, citando cada afirmación con su [id]. Prueba "caffeine", "exercise" o "morning light" y velo tejer las notas coincidentes — con fuentes.

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 — el VectorStore compartido.
  • backend/ai_openai.py / backend/ai_gemini.py — las notas de investigación, el retrieval top-k y un prompt de resumir-con-citas.
  • frontend/app/page.tsx — caja de tema + resumen 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.

Descargar cited-summary.zip

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

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

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
  • backend/store.py
  • 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