This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
# Run the application
./run.sh
# or manually:
cd backend && uv run uvicorn app:app --reload --port 8000
# Install dependencies
uv sync
# Install dev dependencies (required for linting/formatting)
uv sync --group dev
# Add a new dependency
uv add package_name
# Run all tests
uv run pytest
# Run a single test file
uv run pytest backend/tests/test_rag_system.py
# Run a single test
uv run pytest backend/tests/test_rag_system.py::TestClassName::test_method_name
# Format code (modifies files: isort → black → flake8 → mypy)
./scripts/format.sh
# Lint only, no modifications
./scripts/lint.sh- Web UI: http://localhost:8000
- API docs: http://localhost:8000/docs
- Requires
ANTHROPIC_API_KEYin a.envfile at the project root
This is a RAG (Retrieval-Augmented Generation) chatbot for course materials. FastAPI serves both the API and the vanilla JS frontend as static files.
POST /api/query— main query endpoint, returns{ answer, sources, source_links, session_id }GET /api/courses— returns course catalog stats{ total_courses, course_titles }POST /api/clear-session— clears a session by{ session_id }
- Frontend (
frontend/script.js) sendsPOST /api/querywith{ query, session_id } app.pycreates a session if none exists, delegates toRAGSystem.query()RAGSystemfetches conversation history fromSessionManager, then callsAIGenerator.generate_response()AIGeneratorruns a tool-calling loop (max 2 rounds) with the Claude API:- Claude may call
search_course_content(semantic chunk search) orget_course_outline(lesson list) - Tool results are appended to the message list and sent back to Claude
- After max rounds, a final API call without tools forces a text response
- Claude may call
ToolManagercollectslast_sourcesandlast_source_linksfrom whichever tool ran last- Response, sources, and lesson links are returned to the frontend
- Frontend renders the answer as Markdown (
marked.js) with a collapsible sources block
- Course name resolution: Partial/fuzzy course names are resolved via a semantic search against the
course_catalogChromaDB collection before filteringcourse_content. This lets Claude pass "MCP" and still find "Introduction to MCP Servers". - Dual ChromaDB collections:
course_catalogstores one document per course (title + metadata includinglessons_json).course_contentstores all text chunks withcourse_title/lesson_numbermetadata for filtered search. - Session storage: Sessions are in-memory only — they are lost on server restart.
SessionManagerkeeps the last 2 exchange pairs (4 messages) per session. Conversation history is injected into the system prompt, not the message list. - AI generation config:
AIGeneratorusestemperature=0andmax_tokens=800. Model is set inconfig.py(ANTHROPIC_MODEL). These are not exposed via env vars — change them in code. - Deduplication on startup:
add_course_folder()checks existing titles incourse_catalogand skips already-ingested courses.
Course files (.txt, .pdf, .docx) in docs/ must follow this structure for .txt — .pdf/.docx support is parsed but the required header fields are the same:
Course Title: <title>
Course Link: <url>
Course Instructor: <name>
Lesson 0: <title>
Lesson Link: <url>
<lesson content>
Lesson 1: <title>
...
DocumentProcessor splits content into sentence-aware chunks (800 chars, 100 char overlap). The first chunk of each lesson is prefixed with "Lesson N content: ..." for retrieval context.
- Create a class extending
Tool(ABC insearch_tools.py) implementingget_tool_definition()andexecute() - Register it:
self.tool_manager.register_tool(your_tool)inRAGSystem.__init__() - If it should surface sources in the UI, add
last_sourcesandlast_source_linksinstance attributes —ToolManager.get_last_sources()checks all registered tools for these