Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -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 <package>`
- Remove a package: `uv remove <package>`

## 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: <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
60 changes: 60 additions & 0 deletions flow-diagram.md
Original file line number Diff line number Diff line change
@@ -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
```