diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..cf2d00c10 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,64 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Running the Application + +```bash +# Quick start (from project root) +./run.sh + +# Manual start +cd backend && uv run uvicorn app:app --reload --port 8000 +``` + +App runs at http://localhost:8000. Requires `ANTHROPIC_API_KEY` in a `.env` file at the project root. + +Install dependencies: `uv sync` + +**Always use `uv` for all dependency management — never `pip` directly.** +- Add a package: `uv add ` +- Remove a package: `uv remove ` + +## Architecture + +This is a full-stack RAG chatbot. The **backend** is a FastAPI app served from the `backend/` directory; it also serves the **frontend** (`frontend/`) as static files mounted at `/`. There is no build step for the frontend. + +### Query pipeline (the core flow) + +1. `app.py` — receives `POST /api/query`, creates a session if needed, delegates to `RAGSystem` +2. `rag_system.py` — orchestrates the pipeline: fetches conversation history, calls `AIGenerator` +3. `ai_generator.py` — makes the first Claude API call with `tool_choice: auto` and the search tool definition. If Claude invokes the tool, `_handle_tool_execution()` runs it and makes a second Claude call (without tools) to produce the final answer +4. `search_tools.py` — `CourseSearchTool` wraps `VectorStore.search()` and formats results for Claude; `ToolManager` registers tools and exposes them as Anthropic tool definitions +5. `vector_store.py` — two ChromaDB collections: `course_catalog` (one doc per course, for fuzzy name resolution) and `course_content` (chunked lesson text, for semantic search). Embeddings use `all-MiniLM-L6-v2` +6. `session_manager.py` — in-memory session store; keeps last 2 exchanges (configurable via `MAX_HISTORY`) formatted as a string injected into the system prompt + +### Document ingestion + +`document_processor.py` parses `.txt`/`.pdf`/`.docx` files from `docs/` at startup. Expected file format: +``` +Course Title: +Course Link: <url> +Course Instructor: <name> +Lesson 0: <lesson title> +Lesson Link: <url> +<lesson content...> +Lesson 1: <lesson title> +... +``` + +Chunks are sentence-aware with configurable size (800 chars) and overlap (100 chars). + +### Configuration + +All tunable parameters live in `backend/config.py` (`Config` dataclass): +- `ANTHROPIC_MODEL` — Claude model to use +- `EMBEDDING_MODEL` — sentence-transformers model +- `CHUNK_SIZE`, `CHUNK_OVERLAP`, `MAX_RESULTS`, `MAX_HISTORY` +- `CHROMA_PATH` — where ChromaDB persists data (`./chroma_db` relative to `backend/`) + +### Key design decisions +- The server must be started from the `backend/` directory (paths like `../docs` and `../frontend` are relative to that working directory) +- Course deduplication is by title — re-ingesting the same course title is a no-op +- Tool sources (`last_sources`) are stored on the tool instance and reset after each query cycle +- The second Claude call omits tools to prevent recursive tool use diff --git a/flow-diagram.md b/flow-diagram.md new file mode 100644 index 000000000..37fa16be6 --- /dev/null +++ b/flow-diagram.md @@ -0,0 +1,60 @@ +# RAG Chatbot — Request Flow Diagram + +```mermaid +sequenceDiagram + actor User + participant FE as Frontend<br/>(script.js) + participant API as FastAPI<br/>(app.py) + participant RAG as RAGSystem<br/>(rag_system.py) + participant SM as SessionManager<br/>(session_manager.py) + participant AI as AIGenerator<br/>(ai_generator.py) + participant TM as ToolManager<br/>(search_tools.py) + participant VS as VectorStore<br/>(vector_store.py) + participant DB as ChromaDB + participant Claude as Claude API + + User->>FE: Types message + hits Enter/Send + FE->>FE: Disable input, show loading spinner + FE->>API: POST /api/query<br/>{query, session_id} + + API->>SM: create_session() if no session_id + SM-->>API: "session_1" + API->>RAG: query(query, session_id) + + RAG->>SM: get_conversation_history(session_id) + SM-->>RAG: last 2 exchanges (or null) + + RAG->>AI: generate_response(prompt, history, tools) + + AI->>Claude: messages.create()<br/>system prompt + tool definitions<br/>tool_choice: auto + Claude-->>AI: stop_reason = "tool_use"<br/>search_course_content(query, course_name, lesson_number) + + AI->>TM: execute_tool("search_course_content", ...) + TM->>VS: search(query, course_name, lesson_number) + + VS->>DB: query course_catalog<br/>(resolve course name to exact title) + DB-->>VS: matched course title + + VS->>DB: query course_content<br/>(embedding similarity + where filter) + DB-->>VS: top 5 chunks + metadata + + VS-->>TM: SearchResults + TM->>TM: format results + store sources + TM-->>AI: formatted text chunks + + AI->>Claude: messages.create()<br/>[user → tool_use → tool_result]<br/>(no tools this time) + Claude-->>AI: final answer text + + AI-->>RAG: response string + RAG->>TM: get_last_sources() + reset_sources() + RAG->>SM: add_exchange(session_id, query, response) + RAG-->>API: (answer, sources) + + API-->>FE: {answer, sources, session_id} + + FE->>FE: Remove spinner + FE->>FE: Render answer as Markdown + FE->>FE: Show sources in collapsible details + FE->>FE: Save session_id, re-enable input + FE-->>User: Displays response +```