Recuperador de documentos
La mitad de retrieval de RAG. Un conjunto de chunks de documentos se indexa una
Showcase — Recuperador de documentos
La mitad de retrieval de RAG. Un conjunto de chunks de documentos se indexa una vez en el vector store; tu pregunta regresa los mejores pasajes por cosine similarity, cada uno etiquetado con su id de origen para que el resultado sea trazable. Aquí no hay generación — solo la parte que encuentra el contexto correcto para entregárselo a un modelo el próximo capítulo.
Córrelo
bash bootstrap-secrets.sh # reads ../../../../.env, writes secrets/
docker compose up --build # default: PROVIDER=openai
Abre http://localhost:3000. Córrelo contra Gemini con
PROVIDER=gemini docker compose up --build.
Qué hay aquí
backend/store.py— elVectorStorecompartido en memoria (add + search).backend/ai_openai.py/backend/ai_gemini.py— los chunks de documentos, la función de embedding y el retrieval top-k.frontend/app/page.tsx— caja de pregunta + pasajes rankeados.
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-retriever.zip
cd doc-retriever
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): a document retriever over a chunked knowledge base.
This is the retrieval half of RAG (next chapter): index a set of document
chunks in the vector store, then return the top passages for a question — with
their source ids, so the answer is traceable. No generation yet; just the part
that finds the right context.
"""
from openai import OpenAI
from store import VectorStore
_client = OpenAI()
_MODEL = "text-embedding-3-small"
# A handful of chunks from a fictional product's docs. In a real system these
# come from splitting real documents; the store doesn't care where they're from.
_KB = [
{"id": "plans", "text": "The Free plan includes 3 projects. Pro is $12/month for unlimited projects and priority support."},
{"id": "export", "text": "Export your data any time as CSV or JSON from Settings > Data. Exports include all projects."},
{"id": "sso", "text": "Single sign-on (SAML) is available on the Enterprise plan and is configured under Admin > Security."},
{"id": "api", "text": "The REST API is rate-limited to 100 requests per minute. Keys are created under Settings > API."},
{"id": "delete", "text": "Deleting a project is permanent and cannot be undone. Deleted data is purged after 30 days."},
{"id": "invite", "text": "Invite teammates from Members > Invite. Invited users get Editor access by default."},
{"id": "billing", "text": "Plan changes are prorated. Downgrades take effect at the end of the current billing cycle."},
{"id": "mobile", "text": "The mobile app supports viewing and commenting; editing requires the web app."},
]
_store: VectorStore | None = None
def _embed(texts: list[str]) -> list[list[float]]:
return [item.embedding for item in _client.embeddings.create(model=_MODEL, input=texts).data]
def _get_store() -> VectorStore:
global _store
if _store is None: # build the index once
_store = VectorStore(_embed)
_store.add(_KB)
return _store
def run(question: str) -> str:
store = _get_store()
hits = store.search(question.strip(), k=4)
rows = [f"{score:.3f} [{item['id']}] {item['text']}" for score, item in hits]
return f"Top passages for your question (index of {store.size()} chunks):\n\n" + "\n".join(rows)
backend/ai_gemini.py
"""Showcase 1 (Gemini): a document retriever over a chunked knowledge base.
Same store, same chunks, Gemini's embedding model.
"""
import os
from google import genai
from store import VectorStore
_client = genai.Client(api_key=os.environ["GEMINI_API_KEY"])
_MODEL = "gemini-embedding-001"
_KB = [
{"id": "plans", "text": "The Free plan includes 3 projects. Pro is $12/month for unlimited projects and priority support."},
{"id": "export", "text": "Export your data any time as CSV or JSON from Settings > Data. Exports include all projects."},
{"id": "sso", "text": "Single sign-on (SAML) is available on the Enterprise plan and is configured under Admin > Security."},
{"id": "api", "text": "The REST API is rate-limited to 100 requests per minute. Keys are created under Settings > API."},
{"id": "delete", "text": "Deleting a project is permanent and cannot be undone. Deleted data is purged after 30 days."},
{"id": "invite", "text": "Invite teammates from Members > Invite. Invited users get Editor access by default."},
{"id": "billing", "text": "Plan changes are prorated. Downgrades take effect at the end of the current billing cycle."},
{"id": "mobile", "text": "The mobile app supports viewing and commenting; editing requires the web app."},
]
_store: VectorStore | None = None
def _embed(texts: list[str]) -> list[list[float]]:
return [e.values for e in _client.models.embed_content(model=_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:
store = _get_store()
hits = store.search(question.strip(), k=4)
rows = [f"{score:.3f} [{item['id']}] {item['text']}" for score, item in hits]
return f"Top passages for your question (index of {store.size()} chunks):\n\n" + "\n".join(rows)
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