A research editor: write and ask questions in a markdown document, pull supporting extracts from a ground-truth documents (Wikipedia, in this case), and edit the page with an LLM.
- Embeddings: sentence-transformers (
all-MiniLM-L6-v2by default) - Vector store: ChromaDB (ephemeral, per browser session)
- Generation: pydantic-ai
- Wikipedia: Action API via
wikipedia-api, with rate limiting and page cache - API: FastAPI + uvicorn
- Frontend: Vite, React 19, TypeScript, TipTap (math via KaTeX)
Workspace packages (uv): backend, evals, experiments.
Requires Python 3.11+ and Node.js 18+.
uv sync
cd frontend && npm install| Variable | Purpose |
|---|---|
OPENAI_API_KEY |
Required for LLM calls |
LLM_MODEL |
pydantic-ai model id (see .env.example) |
EMBEDDING_MODEL |
sentence-transformers model |
CORS_ORIGINS |
Comma-separated origins (default http://localhost:5173) |
SESSION_TTL_SECONDS |
Cookie session lifetime (default 1800) |
WIKI_USER_AGENT |
Identifying User-Agent with a real contact (Wikimedia policy) |
WIKI_MAX_PAGES / WIKI_SEARCH_RESULTS / WIKI_CONCURRENCY / WIKI_MAX_REQUESTS_PER_MINUTE |
Wikipedia fetch limits |
DEBUG |
Enable Logfire / pydantic-ai instrumentation when true |
From the project root, two terminals:
| Terminal | Command | Port |
|---|---|---|
| Backend | uv run backend/main.py |
8000 |
| Frontend | cd frontend && npm run dev |
5173 |
Open http://localhost:5173. Vite proxies /api/* and /health to the backend.
- Cookie session — Each browser gets a session that stores fetched Wikipedia page text and an in-memory Chroma index of those pages.
- Search mode (
POST /api/ask,mode: "search") — Enrich the query → search/fetch Wikipedia → synthesize a markdown answer + extracts → place source blocks next to matching sentences. Empty pages use the “new page” prompts; non-empty pages use “add to page” prompts. Progress streams as NDJSON phase events (enrich,search,synthesize,insert). - Edit mode (
mode: "edit") — LLM returns a JSON Patch over the block list; protected text is diverted to suggestions instead of applied. - Find in sources — Highlight text and retrieve matching passages from the session corpus (
POST /api/find_in_sources). - Source proxy —
GET /api/source?url=...serves Wikipedia HTML for in-editor iframes.
GET /health— livenessPOST /api/ask— NDJSON stream:{type:"phase", phase}then{type:"complete", result}or{type:"error", message}. Body:{"query", "editor_state", "mode": "search"|"edit"}POST /api/find_in_sources—{"query", "editor_state"}→{blocks}GET /api/source?url=...— proxied source HTML
backend/backend/
api/ Routes + request schemas
pipeline/ Search (enrich→search→synthesize→insert) and edit (JSON Patch)
sessions.py Cookie-backed session + per-session Chroma
wikipedia_client.py Rate-limited Wikipedia fetch + cache
vector_store.py Ephemeral Chroma + sentence matching for source placement
source_proxy.py In-app Wikipedia HTML proxy
models.py EditorState / PageBlock domain models
main.py FastAPI app
backend/main.py Dev launcher (`uv run backend/main.py`)
frontend/ TipTap editor UI
evals/ Pipeline evaluation scripts
experiments/ Ad-hoc retrieval / embedding experiments
See backend/README.md, frontend/README.md, and evals/README.md for package-specific notes.