The RAG (Retrieval-Augmented Generation) pipeline in Mesa de Ayuda IA is divided into two distinct phases with different timing characteristics. Phase 1 — offline ingestion runs once (or whenever the knowledge base is updated) viaDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/geremyjampiersalasgarcia-eng/Caso_Practico_Semillero_IA/llms.txt
Use this file to discover all available pages before exploring further.
python scripts/ingest.py; it reads the raw text files, chunks them, generates vector embeddings, and persists everything to ChromaDB on disk. Phase 2 — online retrieval runs on every user query at inference time; it embeds the question with the same model, performs a cosine similarity search against the appropriate ChromaDB collection, and injects the top-4 matching chunks into the agent’s system prompt before calling Gemini. Separating these two phases means the expensive embedding work happens once offline, and every live query enjoys sub-second retrieval from the local vector database without any external calls beyond the Gemini LLM itself.
Phase 1: Document Ingestion
The ingestion pipeline is a four-stage sequential process that transforms raw.txt files into searchable vector collections.
Load — loader.py
load_documents() scans a directory for .txt and .pdf files using LangChain’s TextLoader (UTF-8 encoding) and PyPDFLoader respectively. Each file is read into a LangChain Document object and enriched with metadata — specifically a filename field that is later surfaced in AgentResult.sources so users know which document each answer came from.Split — splitter.py
split_documents() passes the raw Document list through RecursiveCharacterTextSplitter configured with chunk_size=1000, chunk_overlap=200, and separators ["\n\n", "\n", ".", " ", ""]. The splitter tries each separator in order, so paragraph breaks (\n\n) are preferred over sentence breaks (.) and word breaks ( ), preserving semantic coherence within each chunk.Embed — embeddings.py
Each chunk’s text is converted into a high-dimensional numerical vector using
GoogleGenerativeAIEmbeddings with model="models/gemini-embedding-001". The embedding function is shared between the ingestion script and the query-time retriever — the same model that creates the vectors is used to embed user questions, guaranteeing that similarity comparisons are meaningful.Store — vectorstore.py
index_documents() calls get_vectorstore() to initialize (or open) the appropriate ChromaDB collection, then calls vectorstore.add_documents(documents) to persist the embedded chunks. Each collection is stored under settings.CHROMA_PERSIST_DIR (default: data/chroma_db/), meaning the vectors survive backend restarts without re-ingestion.Document-to-Collection Mapping
Each source document is ingested into its own dedicated ChromaDB collection, providing clean knowledge isolation between agents:| Source Document | ChromaDB Collection | Agent Owner | Approx. Chunks |
|---|---|---|---|
01_Catalogo_Productos_Precios.txt (~1,200 chars) | col_catalogo | agente_catalogo, agente_multimodal | 2 |
02_Politicas_Comerciales_Descuentos_Credito.txt (~1,000 chars) | col_politicas | agente_politicas | 1–2 |
03_Proceso_Ventas_CRM.txt (~1,400 chars) | col_proceso_ventas | agente_proceso_ventas | 2 |
Chunk counts are approximate and depend on the exact content boundaries. Because the source documents are short (~1,000–1,500 characters), the splitter generates very few chunks per file — which is intentional. Fewer, larger chunks mean each retrieval result carries more complete context about a topic, reducing the chance of a partial answer.
Phase 2: Query-time Retrieval
At inference time,retrieve_context() in retriever.py performs the following steps for every agent that is invoked:
Embed the question
The user’s question is passed to the same
GoogleGenerativeAIEmbeddings instance used during ingestion. This produces a vector in the same embedding space as the stored chunks, making cosine distances meaningful.Cosine similarity search
ChromaDB’s
similarity_search(query, k=4) computes the cosine similarity between the question vector and every stored chunk vector in the target collection, returning the top_k=4 closest matches.Build retrieval context
The base agent iterates over the returned documents, assembling a
context_text string with labeled fragments and a sources list containing each document’s filename, a 200-character snippet, and its relevance score.Inject into prompt
The retrieved context is appended to the agent’s system prompt before the Gemini LLM call:
system_content = f"{base_prompt}\n\n### CONTEXTO RECUPERADO:\n{context_text}". The LLM never answers from its pre-trained weights alone — every response is grounded in the retrieved document fragments.Why top-k = 4?
With source documents of only 1,000–1,500 characters that produce 1–2 chunks each, retrieving 4 chunks covers the entirety (or nearly all) of the relevant collection. Requesting more chunks would simply return lower-relevance content and bloat the prompt unnecessarily, while requesting fewer might miss complementary information that spans a chunk boundary.ChromaDB Persistence
ChromaDB is run in embedded local mode, storing the vector index on disk rather than requiring a separate database service. All three collections share a single persist directory:- One collection per agent — knowledge isolation prevents cross-contamination between domains. An
agente_politicasquery never accidentally retrieves catalog text. - Persistence across restarts — the
Chroma(persist_directory=...)argument means the index is automatically reloaded whenget_vectorstore()is called after a backend restart, with no warm-up delay. - Re-indexing — running
python scripts/ingest.pyagain clears the existing collections and rebuilds them from scratch. This is the recommended workflow whenever source documents are updated.
Chunking Strategy
TheRecursiveCharacterTextSplitter is configured with the following parameters:
| Parameter | Value | Rationale |
|---|---|---|
chunk_size | 1000 characters | Matches the approximate length of each source document, so each chunk holds a coherent topic section |
chunk_overlap | 200 characters | Ensures that rules or prices that fall exactly at a split boundary are fully captured in at least one chunk |
separators | ["\n\n", "\n", ".", " ", ""] | Prioritizes semantic splits (paragraphs, lines) over mechanical splits (mid-sentence) |
01_Catalogo_Productos_Precios.txt (~1,200 chars):
| Chunk | Approx. Content |
|---|---|
| Chunk 1 | Smartphone product lines (Patito Lite, Patito Pro) — specs, prices, availability |
| Chunk 2 | Laptop product lines + pricing notes (the last 200 chars of Chunk 1 repeat here due to overlap) |
The overlap means the LLM always has enough surrounding context to correctly interpret a price or policy that sits at a document section boundary. Without overlap, a chunk ending with “precio de lista: USD” and a chunk beginning with “1,299 — disponible en stock” would each be incomplete.
Embedding Model
Mesa de Ayuda IA usesmodels/gemini-embedding-001 from Google’s Generative AI platform for all vector operations:
- Spanish text quality —
gemini-embedding-001is trained on multilingual corpora including Spanish, producing embeddings that capture semantic similarity for Spanish sales terminology accurately. A pure English embedding model would underperform on queries like “descuento máximo sin autorización del gerente”. - Gemini ecosystem consistency — using the same vendor for both the embedding model (
gemini-embedding-001) and the generative LLM (gemini-flash-lite-latest) keeps the integration surface minimal: a singleGOOGLE_API_KEYcovers both, and both are accessed through thelangchain-google-genaipackage. - Semillero IA requirement — the Google Gemini stack was specified as the required AI platform for this project, and
gemini-embedding-001is the current production embedding model in that ecosystem.