--- name: nodetool-rag-indexing description: "Build NodeTool document ingestion, vector indexing, retrieval, and RAG pipelines." featured: true --- You help users build Retrieval-Augmented Generation (RAG) pipelines in NodeTool. # RAG Architecture ``` INDEXING: Documents → Load → Split → Embed → Store (vector collection) QUERY: Question → Embed → Search → Format → LLM → Answer ``` # Vector Store Backends NodeTool's vector store (`@nodetool-ai/vectorstore`) is backend-pluggable. The workflow nodes are the same regardless of backend — you pick the backend via configuration. | Backend | Best for | Notes | |---------|----------|-------| | **SQLite-vec** | Default, local, embedded | No external service | | **ChromaDB** | Self-host / remote | `CHROMA_URL`, `CHROMA_PATH`, `CHROMA_TOKEN` | | **Pinecone** | Managed cloud | API-key based | | **Supabase (pgvector)** | Postgres-backed | Pairs with Supabase auth/storage | There are **no FAISS nodes**; the backends above cover local and hosted use. # Vector Nodes (`vector.*`) All RAG nodes live under the single `vector.*` namespace (not `vector.chroma.*` or `vector.faiss.*`). | Node | Purpose | |------|---------| | `vector.Collection` | Reference/select a collection by name (the collection ref other nodes consume) | | `vector.IndexTextChunk` | Index a single text chunk with its embedding | | `vector.IndexString` | Index a string value | | `vector.IndexAggregatedText` | Index aggregated text | | `vector.IndexEmbedding` | Index a precomputed embedding | | `vector.IndexImage` | Index an image | | `vector.QueryText` | Vector similarity search over text | | `vector.QueryImage` | Vector similarity search over images | | `vector.HybridSearch` | Vector + keyword search (best accuracy) | | `vector.GetDocuments` | Retrieve specific documents | | `vector.Count` | Count documents in a collection | | `vector.Peek` | Preview collection contents | | `vector.RemoveOverlap` | De-duplicate overlapping chunks in results | Query nodes (`QueryText`, `QueryImage`, `HybridSearch`) output `ids`, `documents`, `metadatas`, and `distances` (HybridSearch also returns `scores`). # Document Loading & Splitting | Node | Namespace | Purpose | |------|-----------|---------| | `Code` | `nodetool.code` | Enumerate files with `await workspace.list(dir)` | | `LoadDocumentFile` | `nodetool.document` | Load a PDF/TXT/MD into a document | | `Chunk` | `nodetool.text` | Fixed-size word chunking with overlap (general purpose) | | `RegexSplit` | `nodetool.text` | Structure-aware splitting on a delimiter pattern | ## Chunk Size Guidance | Content type | Chunk size | Overlap | |--------------|-----------|---------| | Technical docs | 200-500 tokens | 50 tokens | | Prose/articles | 300-600 tokens | 75 tokens | | Code | 100-300 tokens | 25 tokens | # Indexing — Workflow Pattern ``` ListFiles → LoadDocumentFile → Chunk → IndexTextChunk(collection) ``` Pair every index/query node with a `vector.Collection` node (or a collection name) so they target the same store. Use the **same embedding model** for indexing and querying. # Indexing — HTTP API ```bash # Index a file into a collection curl -X POST http://localhost:7777/api/collections//index \ -H "Authorization: Bearer TOKEN" \ -H "Content-Type: application/json" \ -d '{"file_path": "/path/to/document.pdf"}' ``` The server resolves the collection, runs its ingestion workflow if one is registered, otherwise falls back to split → embed → store. # Query — Workflow Pattern ``` ChatInput → HybridSearch(collection, top_k) → FormatText → Agent → Output ``` | Node | Purpose | |------|---------| | `ChatInput` | User question | | `vector.HybridSearch` | Vector + keyword retrieval (best accuracy) | | `vector.QueryText` | Vector-only retrieval (faster) | | `FormatText` | Build the context string for the LLM | | `Agent` | Generate the answer from context + question | | `Output` | Return the answer | # Complete RAG Example ## Index ``` ListFiles("/docs/") → LoadDocumentFile → Chunk(length=400, overlap=50) ↓ IndexTextChunk(collection="my-docs") ``` ## Query ``` ChatInput("What is...?") → HybridSearch(collection="my-docs", top_k=5) ↓ FormatText(template="Context:\n{documents}\n\nQuestion: {query}") ↓ Agent(model=gpt-5.4, system="Answer using only the context provided.") ↓ Output ``` # Environment Variables ```bash # ChromaDB backend (only when using Chroma — SQLite-vec needs no config) CHROMA_URL= # Remote Chroma URL (empty = local) CHROMA_PATH=~/.local/share/nodetool/chroma # Local storage path CHROMA_TOKEN= # Optional auth token ``` The embedding model is chosen on the index/query nodes via model selection (e.g. `text-embedding-3-small`, or a local sentence-transformers model). # Common Pitfalls - **Embedding model mismatch**: use the same embedding model for indexing and search. - **Chunks too large**: dilute the LLM context — keep to 200-500 tokens. - **Chunks too small**: sentences get fragmented; use 10-20% overlap. - **Empty collection**: index before querying — an unindexed collection returns nothing. - **Wrong node names**: it's `vector.IndexTextChunk` / `vector.QueryText` / `vector.HybridSearch`, not `IndexTextChunks` / `TextSearch`, and there is no `vector.chroma.*`/`vector.faiss.*` namespace. - **No `nodetool collections` CLI**: manage collections through the editor UI or the `/api/collections/...` endpoints.