跪拜 Guibai
← All articles
JavaScript · Artificial Intelligence · Full Stack

Ingesting a 1.2M-Word Novel into a Vector DB for RAG, Step by Step

By dzhd ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

RAG pipelines look trivial in demos but break at scale on details like chunk boundaries, dimension mismatch, and type coercion. This walkthrough surfaces those hidden failure points and shows how a few defensive patterns keep a real-world ingestion job from silently corrupting its own data.

Summary

A full ingestion-to-answer pipeline processes the classic wuxia novel "Tian Long Ba Bu" into a vector database. The EPUB is split into 50 chapters, chunked into roughly 2,400 overlapping text segments, and embedded into 1,024-dimensional vectors using Alibaba's text-embedding-v3 model via an OpenAI-compatible endpoint. All vectors land in a Milvus collection indexed with IVF_FLAT and cosine similarity.

A separate retrieval module performs pure vector search, while the RAG module assembles a prompt from the top-k fragments and feeds it to a Chat model to generate natural-language answers about characters' martial arts skills. The design emphasizes defensive coding: parallel embedding with Promise.all, primary keys that encode business semantics, and return-value normalization to prevent NaN propagation.

The stack uses LangChain's community loaders and splitters, but the real substance is in the engineering decisions—why 1024 dimensions instead of 1536, why nlist=1024, and how a 50-character overlap prevents context from falling on chunk boundaries.

Takeaways
An OpenAI-compatible base URL lets LangChain's OpenAIEmbeddings and ChatOpenAI call Alibaba's DashScope models without any SDK changes.
Setting splitChapters to true on EPubLoader is not optional for large books; loading a 1.2M-word novel whole would exceed token limits and crash memory.
A 50-character overlap between chunks prevents key dialogue or plot points from landing exactly on a split boundary, which would break retrieval accuracy.
text-embedding-v3 supports on-demand dimensionality reduction; dropping from 1536 to 1024 dimensions loses under 2% precision but saves 33% on vector storage.
Encoding book ID, chapter number, and chunk index into the primary key (e.g., "1_3_7") makes every search result self-describing without extra lookups.
Wrapping insert_cnt with Number(...) || 0 prevents a single undefined or NaN return from permanently poisoning the total-inserted counter in JavaScript.
Setting the Chat model's temperature to 0.1 suppresses creative hallucination for fact-bound Q&A over a fixed corpus.
loadCollection is wrapped in a silent try-catch because the collection may already be loaded; the error is recoverable and should not halt execution.
Conclusions

Most RAG tutorials stop at "search + LLM," but the real engineering lives in the ingestion pipeline: chunk strategy, dimension tuning, index parameters, and type-safety at the database boundary.

The project treats the OpenAI API format as a universal interface layer, decoupling the embedding model, chat model, and vector database so any component can be swapped by changing environment variables alone.

RecursiveCharacterTextSplitter's recursive degradation from semantic boundaries (paragraphs, sentences) down to hard character cuts is a practical compromise that avoids the worst fragmentation without requiring a custom splitter.

Parallel embedding with Promise.all is safe here because each chunk's API call is stateless, but the article correctly notes that rate limits become the real ceiling at higher volumes.

The prompt's five constraint instructions are not boilerplate; each one counters a specific RAG failure mode: vagueness, myopic focus on the first fragment, hallucination, contradiction with source material, and unsupported claims.

Concepts & terms
IVF_FLAT index
An approximate nearest-neighbor index that clusters all vectors into nlist groups via K-Means during build time. At query time, it searches only the nearest few clusters, trading a small accuracy loss for a large speed gain over brute-force scan.
Cosine similarity
A metric that measures the angle between two vectors, ignoring their magnitudes. It ranges from -1 (opposite) to 1 (identical direction), making it suitable for semantic search where text length should not influence the similarity score.
RecursiveCharacterTextSplitter
A LangChain text splitter that tries to break text at increasingly granular natural boundaries—paragraphs, newlines, periods, exclamation marks—before falling back to a hard character-count cut, preserving as much semantic coherence as possible within each chunk.
RAG (Retrieval-Augmented Generation)
A pattern that retrieves relevant documents or fragments from a knowledge base and injects them into an LLM's prompt, grounding the generated answer in retrieved evidence rather than relying solely on the model's parametric knowledge.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗