Skip to content

Commit 0ee286d

Browse files
Merge branch 'dev'
2 parents d1d7bc6 + 0c74f20 commit 0ee286d

19 files changed

Lines changed: 751 additions & 33 deletions

ARCHITECTURE.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,14 @@ wdoc.__init__() ── Configuration & validation
8989

9090
| Role | Default Model | Purpose |
9191
|------|--------------|---------|
92-
| **Main model** | `gemini-3.1-pro` | Answering, summarizing |
93-
| **Eval model** | `gemini-2.5-flash` | Document relevance checking (cheap/fast) |
92+
| **Main model** | `deepseek-v4-pro` | Answering, summarizing |
93+
| **Eval model** | `deepseek-v4-flash` | Document relevance checking (cheap/fast) |
9494
| **Embed model** | `text-embedding-3-small` | Dense vector embeddings |
9595

9696
All models are loaded through **LiteLLM**, supporting 100+ providers (OpenAI, Anthropic, Google, Mistral, Ollama, OpenRouter, etc.).
9797

98+
The defaults are duplicated across `wdoc/utils/env.py`, the docs, `README.md`, `SKILL.md`, this file, and `docker/env.example`. Use `./bump_default_models.sh` at the repo root to change them in one shot (dry-run by default, `--apply` to write).
99+
98100
---
99101

100102
## Query Pipeline (RAG)

CLAUDE.md

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
wdoc is a production-grade RAG system for document summarization, searching, and querying across 20+ file types. It serves as both a CLI tool (via Google Fire) and a Python library (`from wdoc import wdoc`). All LLM calls go through LiteLLM (100+ providers). See `ARCHITECTURE.md` for detailed data flow and component diagrams.
8+
9+
## Commands
10+
11+
### Install
12+
```bash
13+
pip install -e .[full]
14+
```
15+
16+
### Test
17+
```bash
18+
# All tests
19+
pytest tests --quiet
20+
21+
# By category (basic = no API keys needed, api = needs credentials)
22+
pytest -m basic tests/test_wdoc.py
23+
pytest -m api tests/test_wdoc.py
24+
25+
# Single test
26+
pytest tests/test_wdoc.py::test_wdoc_version
27+
28+
# Parallelized
29+
pytest -n auto -m basic tests/test_parsing.py
30+
31+
# CLI integration tests
32+
cd tests && ./test_cli.sh api
33+
```
34+
35+
Test environment variables: `PYTEST_IS_TESTING_WDOC=true`, `WDOC_TYPECHECKING=crash`, `WDOC_DISABLE_EMBEDDINGS_CACHE=true`.
36+
37+
### Lint / Format
38+
```bash
39+
ruff format wdoc/
40+
pre-commit run --all-files # runs ruff-format + pytest (on pre-merge)
41+
```
42+
43+
## Architecture Essentials
44+
45+
**Entry points**: `__main__.py` (CLI via Google Fire) → `wdoc.py` (main orchestration class, ~3000 lines).
46+
47+
**Named AI personas** drive the RAG pipeline in sequence:
48+
- **Raphael** — rephrases user query into alternatives
49+
- **Eve** — evaluates chunk relevance (cheap eval model)
50+
- **Anna** — extracts answers from relevant chunks (main model)
51+
- **Carl** — combines intermediate answers hierarchically
52+
- **Sam** — summarizes chunks (summarization task)
53+
54+
Each persona can be customized via `WDOC_{NAME}_INSTRUCTIONS` env vars.
55+
56+
**Configuration**: `utils/env.py` defines `EnvDataclass` — a frozen dataclass loading 50+ `WDOC_*` environment variables. CLI args override env vars.
57+
58+
**Document loading**: `utils/loaders/` has per-filetype loaders dispatched by `load_one_doc()`. PDF loading tries 15 parser backends and picks the best. URL loading has Jina → Playwright → Selenium fallback chain.
59+
60+
**Caching**: four layers — LLM responses (SQLite), embeddings (CacheBackedEmbeddings), parsed documents (joblib.Memory), and document content hashing.
61+
62+
**Type checking**: Beartype runtime checking, controlled by `WDOC_TYPECHECKING` env var (crash/warn/disabled).
63+
64+
## Adding New Settings
65+
66+
Any new setting (CLI argument or environment variable) **must** be documented in:
67+
- `wdoc/docs/help.md` — describe the setting, its type, default value, and accepted values.
68+
- `wdoc/docs/examples.md` — add usage examples where appropriate.
69+
70+
New settings should be either:
71+
- A **CLI argument** (defined in `wdoc.py`'s main class), or
72+
- A **`WDOC_*` environment variable** (defined in `wdoc/utils/env.py`'s `EnvDataclass`).
73+
74+
**Environment variables are re-read on every access, not just at declaration.** The `EnvDataclass.__getattribute__` method checks `os.environ` at access time when the dataclass is frozen, so changing an env var between wdoc instantiations (or even at runtime) takes effect without reimporting.
75+
76+
## Variables in `wdoc/utils/misc.py` to Keep Updated
77+
78+
When adding new CLI arguments or loader-specific parameters, update these dicts in `wdoc/utils/misc.py`:
79+
- `filetype_arg_types` — maps loader-specific argument names to their types (e.g. `"whisper_lang": str`).
80+
- `extra_args_types` — maps extra wdoc instantiation arguments to their types. It merges `filetype_arg_types` automatically.
81+
- `DocDict.allowed_keys` — derives from `filetype_arg_types` keys automatically, but verify new keys appear.
82+
83+
## Adding Support for a New Filetype
84+
85+
1. **Create a loader** in `wdoc/utils/loaders/` — add a file (e.g. `myformat.py`) containing a function `load_myformat(path, file_hash, ...) -> List[Document]`. Use the `@debug_return_empty` and `@optional_strip_unexp_args` decorators (see `txt.py` for a minimal example).
86+
2. **Register the filetype** — add `"myformat"` to the `LOADABLE_FILETYPE` list in `wdoc/utils/loaders/__init__.py`.
87+
3. **Add loader-specific args** (if any) to `filetype_arg_types` in `wdoc/utils/misc.py`.
88+
4. **Document it** — add the filetype and its arguments to `wdoc/docs/help.md`, and add examples to `wdoc/docs/examples.md`.
89+
5. **Auto-detection** (optional) — if the filetype corresponds to a file extension, add a mapping in the auto-detection logic so `--filetype=auto` can infer it.
90+
91+
## Bumping the Default Models
92+
93+
The two default LLM identifiers (`WDOC_DEFAULT_MODEL` and `WDOC_DEFAULT_QUERY_EVAL_MODEL`) are duplicated across `wdoc/utils/env.py`, `wdoc/docs/help.md`, `SKILL.md`, `README.md`, `ARCHITECTURE.md`, and `docker/env.example`. Use the repo-root helper instead of editing each file by hand:
94+
95+
```bash
96+
./bump_default_models.sh <NEW_STRONG_MODEL> <NEW_EVAL_MODEL> # dry-run preview
97+
./bump_default_models.sh <NEW_STRONG_MODEL> <NEW_EVAL_MODEL> --apply # write
98+
```
99+
100+
`env.py` is the source of truth for the current values. The script replaces both the full id (`provider/path/name`) and the basename, and re-syncs the `KEY=VALUE` lines in `docker/env.example` independently (so it recovers from prior drift). It never commits; review with `git diff` and commit yourself.
101+
102+
## Building Documentation
103+
104+
To regenerate the Sphinx autodoc files, run from the repo root:
105+
```bash
106+
sphinx-apidoc -o docs/source/ wdoc --force
107+
```
108+
109+
## Key Conventions
110+
111+
- `utils/misc.py` is a large utility module (~53KB) — search it before creating new helpers.
112+
- Piped input to CLI is auto-detected as JSON, TOML, URLs, or file paths (`__main__.py`).
113+
- `--private` / `WDOC_PRIVATE_MODE=true` blocks all outbound connections and redacts API keys.
114+
- Binary FAISS embeddings (`WDOC_FAISS_COMPRESSION`) give ~32x compression; implemented in `utils/customs/binary_faiss_vectorstore.py`.
115+
- Semantic batching clusters intermediate answers before combining (scipy hierarchical clustering).
116+
- Use `python` not `python3` in commands.

README.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
[![PyPI version](https://badge.fury.io/py/wdoc.svg)](https://badge.fury.io/py/wdoc)
2+
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/thiswillbeyourgithub/wdoc)
23

34
# wdoc
45

@@ -82,9 +83,9 @@ wdoc --path=$link --task=query --filetype="online_pdf" --query="What does it say
8283
2. cut the text into chunks and create embeddings for each
8384
3. Take the user query, create embeddings for it ('basic') AND ask the default LLM to generate alternative queries and embed those
8485
4. Use those embeddings to search through all chunks of the text and get the 200 most appropriate documents
85-
5. Pass each of those documents to the smaller LLM (default: openrouter/google/gemini-2.5-flash) to tell us if the document seems appropriate given the user query
86+
5. Pass each of those documents to the smaller LLM (default: openrouter/deepseek/deepseek-v4-flash) to tell us if the document seems appropriate given the user query
8687
6. If More than 90% of the 200 documents are appropriate, then we do another search with a higher top_k and repeat until documents start to be irrelevant OR we it 500 documents.
87-
7. Then each relevant doc is sent to the strong LLM (by default, openrouter/google/gemini-3.1-pro-preview) to extract relevant info and give one answer per relevant document.
88+
7. Then each relevant doc is sent to the strong LLM (by default, openrouter/deepseek/deepseek-v4-pro) to extract relevant info and give one answer per relevant document.
8889
8. Then all those "intermediate" answers are 'semantic batched' (meaning we create embeddings, do hierarchical clustering, then create small batch containing several intermediate answers of similar semantics, sort the batch in semantic order too), each batch is combined into a single answer per batch of relevant doc (or after: per batch of batches).
8990
9. Rinse and repeat steps 7+8 (i.e. gradually aggregate batches) until we have only one answer, that is returned to the user.
9091

@@ -97,7 +98,7 @@ wdoc --path=$link --task=summarize --filetype="online_pdf"
9798
```
9899
* This will:
99100
1. Split the text into chunks
100-
2. pass each chunk into the strong LLM (by default openrouter/google/gemini-3.1-pro-preview) for a very low level (=with all details) summary. The format is markdown bullet points for each idea and with logical indentation.
101+
2. pass each chunk into the strong LLM (by default openrouter/deepseek/deepseek-v4-pro) for a very low level (=with all details) summary. The format is markdown bullet points for each idea and with logical indentation.
101102
3. When creating each new chunk, the LLM has access to the previous chunk for context.
102103
4. All summary are then concatenated and returned to the user
103104

@@ -282,6 +283,10 @@ FAQ
282283
no argument but returning a `List[Document]`. Take a look at the `OpenparseDocumentParser`
283284
class for an example.
284285
286+
* **Can `wdoc` add source citations to summaries?**
287+
* Yes! When summarizing documents that have page metadata (like PDFs), `wdoc` automatically adds `[p.N]` citations to bullet points tracking which page the information came from. For multi-file summaries, citations include the filename: `[p.N, file.pdf]`. You can also use `--citation_url_template` to turn these into clickable markdown links pointing to your own document server (e.g. `--citation_url_template="https://my-site.com/docs/{source}#page={page}"`). This feature was developed with Claude Code.
288+
* For the query task, source documents are referenced with clickable anchor links `[N](#document-N)` in the final answer.
289+
285290
* **What should I do if I keep hitting rate limits?**
286291
* The simplest way is to add the `debug` argument. It will disable multithreading,
287292
multiprocessing and LLM concurrency. A less harsh alternative is to set the

SKILL.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,8 +254,8 @@ These load multiple documents and can combine different sources:
254254

255255
| Variable | Default | Description |
256256
|----------|---------|-------------|
257-
| `WDOC_DEFAULT_MODEL` | `openrouter/google/gemini-3.1-pro-preview` | Default strong LLM |
258-
| `WDOC_DEFAULT_QUERY_EVAL_MODEL` | `openrouter/google/gemini-2.5-flash` | Default eval LLM |
257+
| `WDOC_DEFAULT_MODEL` | `openrouter/deepseek/deepseek-v4-pro` | Default strong LLM |
258+
| `WDOC_DEFAULT_QUERY_EVAL_MODEL` | `openrouter/deepseek/deepseek-v4-flash` | Default eval LLM |
259259
| `WDOC_DEFAULT_EMBED_MODEL` | `openai/text-embedding-3-small` | Default embedding model |
260260
| `WDOC_DEFAULT_EMBED_DIMENSION` | `none` | Embedding dimensions to request |
261261

0 commit comments

Comments
 (0)