|
| 1 | +# WDOC Architecture |
| 2 | + |
| 3 | +WDOC is a Retrieval-Augmented Generation (RAG) system that loads documents from 20+ source types, embeds them into a vector store, and supports querying, summarizing, searching, and parsing tasks via LLMs. |
| 4 | + |
| 5 | +--- |
| 6 | + |
| 7 | +## Directory Structure |
| 8 | + |
| 9 | +``` |
| 10 | +wdoc/ |
| 11 | +├── __init__.py # Package init, beartype setup |
| 12 | +├── __main__.py # CLI entry point (Google Fire) |
| 13 | +├── wdoc.py # Main orchestration class (~3000 lines) |
| 14 | +└── utils/ |
| 15 | + ├── env.py # Configuration (EnvDataclass, WDOC_* env vars) |
| 16 | + ├── llm.py # LLM loading via LiteLLM, price tracking |
| 17 | + ├── embeddings.py # Embedding model setup, FAISS vector store |
| 18 | + ├── retrievers.py # Retriever creation (multi-query, parent, SVM, KNN) |
| 19 | + ├── prompts.py # LLM prompt templates (named personas) |
| 20 | + ├── batch_file_loader.py # Parallel multi-file loading with filetype inference |
| 21 | + ├── load_recursive.py # Handles compound/recursive filetypes |
| 22 | + ├── misc.py # Caching, hashing, token counting utilities |
| 23 | + ├── errors.py # Custom exceptions |
| 24 | + ├── logger.py # Loguru-based logging |
| 25 | + ├── filters.py # Document filtering (regex, metadata) |
| 26 | + ├── interact.py # Interactive CLI features |
| 27 | + ├── loaders/ # Per-filetype document loaders |
| 28 | + │ ├── __init__.py # load_one_doc() dispatcher |
| 29 | + │ ├── pdf.py # PDF (15 parser backends) |
| 30 | + │ ├── url.py # URLs (jina, playwright, selenium) |
| 31 | + │ ├── youtube.py # YouTube videos |
| 32 | + │ ├── local_audio.py # Audio transcription (Deepgram) |
| 33 | + │ ├── local_video.py # Video extraction |
| 34 | + │ ├── epub.py # EPUB books |
| 35 | + │ ├── anki.py # Anki flashcards |
| 36 | + │ ├── logseq_markdown.py # Logseq notes |
| 37 | + │ ├── word.py # Word documents |
| 38 | + │ ├── powerpoint.py # PowerPoint |
| 39 | + │ └── json_dict.py # JSON entries |
| 40 | + ├── tasks/ # Task implementations |
| 41 | + │ ├── types.py # wdocTask enum |
| 42 | + │ ├── query.py # Query: retrieve → evaluate → answer → combine |
| 43 | + │ ├── summarize.py # Recursive chunk summarization |
| 44 | + │ ├── search.py # Similarity search (no LLM answering) |
| 45 | + │ ├── parse.py # Raw document extraction |
| 46 | + │ └── shared_query_search.py |
| 47 | + └── customs/ # Custom implementations |
| 48 | + ├── litellm_embeddings.py # LiteLLM-based embeddings |
| 49 | + ├── binary_faiss_vectorstore.py # Binary FAISS (~32x compression) |
| 50 | + ├── compressed_embeddings_cacher.py |
| 51 | + └── fix_llm_caching.py |
| 52 | +``` |
| 53 | + |
| 54 | +--- |
| 55 | + |
| 56 | +## Data Flow |
| 57 | + |
| 58 | +``` |
| 59 | +User Input (CLI / Python API) |
| 60 | + │ |
| 61 | + ▼ |
| 62 | +CLI Parser (__main__.py) ── Google Fire argument parsing |
| 63 | + │ Piped input detection (JSON, TOML, text) |
| 64 | + ▼ |
| 65 | +wdoc.__init__() ── Configuration & validation |
| 66 | + │ |
| 67 | + ├──► Document Loading (batch_file_loader.py) |
| 68 | + │ 1. Infer filetype (regex rules + file magic) |
| 69 | + │ 2. Expand recursive types (directories, playlists, JSON arrays) |
| 70 | + │ 3. Parallel load via joblib (loaders/*.py) |
| 71 | + │ 4. Text splitting (RecursiveCharacterTextSplitter or semantic) |
| 72 | + │ 5. Metadata enrichment (hash, title, source, reading time) |
| 73 | + │ |
| 74 | + ├──► Embedding & Vector Store (embeddings.py) |
| 75 | + │ 1. Initialize embedding model (LiteLLMEmbeddings) |
| 76 | + │ 2. Cache-backed embedding layer |
| 77 | + │ 3. Build FAISS index (standard or binary-compressed) |
| 78 | + │ |
| 79 | + └──► Task Execution |
| 80 | + │ |
| 81 | + ├─ query/search → Retrieval pipeline (see below) |
| 82 | + ├─ summarize → Chunk-by-chunk summarization |
| 83 | + └─ parse → Return raw documents (no LLM) |
| 84 | +``` |
| 85 | + |
| 86 | +--- |
| 87 | + |
| 88 | +## Three LLM Roles |
| 89 | + |
| 90 | +| Role | Default Model | Purpose | |
| 91 | +|------|--------------|---------| |
| 92 | +| **Main model** | `gemini-3.1-pro` | Answering, summarizing | |
| 93 | +| **Eval model** | `gemini-2.5-flash` | Document relevance checking (cheap/fast) | |
| 94 | +| **Embed model** | `text-embedding-3-small` | Dense vector embeddings | |
| 95 | + |
| 96 | +All models are loaded through **LiteLLM**, supporting 100+ providers (OpenAI, Anthropic, Google, Mistral, Ollama, OpenRouter, etc.). |
| 97 | + |
| 98 | +--- |
| 99 | + |
| 100 | +## Query Pipeline (RAG) |
| 101 | + |
| 102 | +The query task uses named AI personas in sequence: |
| 103 | + |
| 104 | +1. **Raphael (Rephraser)** — Expands user query into multiple alternative phrasings |
| 105 | +2. **Vector Store** — Embeds queries and retrieves top-k similar document chunks |
| 106 | +3. **Eve (Evaluator)** — Checks each chunk's relevance to the query (eval model, cheap) |
| 107 | +4. **Anna (Answerer)** — Extracts an answer from each relevant chunk (main model) |
| 108 | +5. **Carl (Combiner)** — Hierarchically clusters and combines intermediate answers into a final response |
| 109 | + |
| 110 | +**Smart top-k expansion**: starts at top_k (default 200); if >90% of documents are relevant, automatically increases top_k and retries until diminishing returns. |
| 111 | + |
| 112 | +--- |
| 113 | + |
| 114 | +## Summarization Pipeline |
| 115 | + |
| 116 | +1. Split document into chunks (with overlap) |
| 117 | +2. **Sam (Summarizer)** summarizes each chunk, passing the previous chunk's summary as context |
| 118 | +3. If `summary_n_recursion > 0`, recursively summarize the summaries |
| 119 | +4. Returns a `wdocSummary` dataclass with the full result tree |
| 120 | + |
| 121 | +--- |
| 122 | + |
| 123 | +## Configuration System |
| 124 | + |
| 125 | +**`utils/env.py`** defines `EnvDataclass`, a frozen dataclass with 50+ fields loaded from `WDOC_*` environment variables. CLI arguments override env vars. |
| 126 | + |
| 127 | +Key variables: |
| 128 | +- `WDOC_DEFAULT_MODEL` / `WDOC_DEFAULT_EMBED_MODEL` / `WDOC_DEFAULT_QUERY_EVAL_MODEL` |
| 129 | +- `WDOC_PRIVATE_MODE` — blocks outbound connections, redacts API keys |
| 130 | +- `WDOC_MAX_CHUNK_SIZE` — max tokens per chunk |
| 131 | +- `WDOC_FAISS_COMPRESSION` — enable binary embeddings |
| 132 | + |
| 133 | +--- |
| 134 | + |
| 135 | +## Document Loaders |
| 136 | + |
| 137 | +Each filetype has a dedicated loader in `utils/loaders/`. The dispatcher (`load_one_doc()`) dynamically imports the `load_{filetype}` function. |
| 138 | + |
| 139 | +**Supported types**: `pdf`, `txt`, `word`, `powerpoint`, `epub`, `url`, `youtube`, `youtube_playlist`, `online_pdf`, `online_media`, `local_audio`, `local_video`, `anki`, `logseq_markdown`, `json_dict`, `recursive_paths`, `json_entries`, `toml_entries`, `string`, `ddg` (DuckDuckGo search). |
| 140 | + |
| 141 | +**PDF parsing** is particularly robust: 15 different parser backends are evaluated and the best result is selected automatically. |
| 142 | + |
| 143 | +**URL loading** has multiple fallbacks: Jina → Playwright → Selenium → others. |
| 144 | + |
| 145 | +--- |
| 146 | + |
| 147 | +## Caching Layers |
| 148 | + |
| 149 | +1. **LLM response cache** — `SQLiteCacheFixed` prevents duplicate LLM calls |
| 150 | +2. **Embedding cache** — `CacheBackedEmbeddings` avoids re-embedding identical content |
| 151 | +3. **Loader cache** — `joblib.Memory` caches parsed documents |
| 152 | +4. **Document hashing** — content_hash / file_hash / all_hash prevent reprocessing |
| 153 | + |
| 154 | +--- |
| 155 | + |
| 156 | +## Error Handling |
| 157 | + |
| 158 | +Custom exceptions in `utils/errors.py`: |
| 159 | +- `NoDocumentsRetrieved` — no documents found in vector store |
| 160 | +- `NoDocumentsAfterLLMEvalFiltering` — all retrieved docs deemed irrelevant |
| 161 | +- `NoRelevantIntermediateAnswers` — LLM found no useful answers |
| 162 | +- `TimeoutPdfLoaderError` — PDF parsing timeout |
| 163 | + |
| 164 | +Fallback strategies: multiple parser/loader backends tried in sequence before failing. |
| 165 | + |
| 166 | +--- |
| 167 | + |
| 168 | +## Key Dependencies |
| 169 | + |
| 170 | +| Category | Libraries | |
| 171 | +|----------|-----------| |
| 172 | +| RAG framework | LangChain, LangChain-Community | |
| 173 | +| LLM abstraction | LiteLLM, LangChain-LiteLLM | |
| 174 | +| Vector store | FAISS | |
| 175 | +| Text splitting | Chonkie (semantic), LangChain (recursive) | |
| 176 | +| Document parsing | Unstructured, OpenParse, BeautifulSoup, lxml | |
| 177 | +| Media | yt-dlp, Deepgram-SDK, pydub | |
| 178 | +| Web scraping | Playwright, Selenium, Jina | |
| 179 | +| Parallel processing | Joblib | |
| 180 | +| Type checking | Beartype (runtime) | |
| 181 | +| Logging | Loguru, Rich | |
| 182 | +| Observability | Langfuse | |
| 183 | +| Caching/storage | SQLAlchemy, LMDB (PersistDict) | |
0 commit comments