This is the FastAPI backend for the EPFL Graph and CEDE chatbots, developed by the Center for Digital Education (CEDE): a modular framework to build and serve educational tutors, the EPFL Graph chatbot and other administrative RAG assistants. All bots are built with LangChain and LangGraph.
The system is designed around a modular, self-discovering bot architecture: each bot is a standalone agent with its own prompts, tools, and conversation graph. New bots are automatically detected at runtime—no manual registration required.
All agents use models from the inference service at EPFL's Research Computing Platform, which guarantees that data is never sent to external providers.
This repository exposes a FastAPI application with OpenAI-compatible streaming endpoints that serve a variety of task-specific AI tutors and assistants. Bots can be tailored for:
- Administrative tasks (e.g. answering questions about institutional docs via RAG)
- Course tutoring (e.g. pedagogical Q&A with classification into theory / practice / admin / unrelated)
- Custom workflows (build any LangGraph topology and plug it in)
app/
├── main.py # FastAPI entry point
├── config.py # INI + environment variable loading
├── bots/
│ ├── base.py # Bot ABC, BotState, model configuration
│ ├── registry.py # Auto-discovery of bot classes via filesystem scanning
│ ├── prompts.py # Recursive Markdown prompt resolution
│ ├── main.py # LLM completion / streaming helpers
│ ├── nodes/ # Reusable LangGraph nodes (classify, model, tools)
│ ├── admin/ # AdminBot + concrete admin bots
│ ├── course/ # CourseBot + pedagogical variants
│ └── graph_chat/ # GraphChatBot
├── interfaces/graphai.py # GraphAI RAG client
├── llms/utils.py # Structured output helpers
└── routers/ # FastAPI public routers
- Auto-discovery: Bots are found by scanning
app/bots/for classes defined in*_bot.pyfiles - No central registry: Drop a new bot directory in the right place and restart—the registry picks it up automatically
- Prompts as Markdown: Prompts are composed recursively from Markdown fragments, allowing easy inheritance and overrides
- Stateless graphs: LangGraph graphs are compiled once at startup (
@cached_property) and reused per request - Streaming-first: All endpoints support streaming message completion via
stream_mode="messages"
- Python 3.12
- A running RAG backend (GraphAI / Elasticsearch) if using RAG-enabled bots
- An RCP API key
# Clone the repository
git clone https://github.com/epflgraph/graphchatbot.git
cd graphchatbot
# Create a virtual environment
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install dependencies
pip install -e .Copy the example configuration file and fill in your credentials:
cp config.ini.example config.iniEdit config.ini to set:
- RCP base url and API key
- GraphAI / Elasticsearch connection details
- Langfuse credentials for tracing
# Standard
python -m app.main
# With auto-reload (development)
uvicorn app.main:app --reload --port 8000The API documentation will be available at http://localhost:8000/docs.
Creating a new bot requires zero modifications to existing code.
-
Create the bot directory
Insideapp/bots/<category>/<botname>/, create:<botname>_bot.py— class definition<prompt_file>.md— system prompt templates for the agent, or omit as needed to fall back to higher-level prompt filestool_description.md— tool-calling hints
-
Pick the right base class
Base Class Use Case AdminBotSingle-tool RAG bot for institutional docs CourseBotCourse tutor with built-in message classification (theory / practice / admin / unrelated) HintingCourseBotCourse tutor that provides hints instead of direct answers DirectCourseBotCourse tutor that gives direct answers Bot(ABC)Fully custom LangGraph topology Each bot class must define:
name: str(unique identifier)groups: list[str](authorized user groups; use[]for public/unrestricted bots)- Any required configuration fields
-
Restart the application — the registry auto-discovers and instantiates the bot.
The built-in prompt system (app/bots/prompts.py) composites bot prompts from recursive Markdown fragments:
{fragment}→ inline another.mdfile (searched upwards from the bot directory){{placeholder}}→ dynamic value filled at runtime viastr.format(...)
from app.bots.registry import init_bots, get_bot
init_bots()
bot = get_bot("MY-BOT-NAME")
print(bot.prompt()) # View resolved prompt
print(bot.build_tools()) # Inspect tool schemas
print(bot.graph) # Verify graph compilesmake test # run unittest discover over tests/ with coverage
make lint # check linting and formatting (no writes)
make lint-fix # auto-fix lint issues and reformat- Async everywhere: All node functions and tools must be
async - Python 3.12 types: Use
list[str],dict[str, ...],str | None - No hardcoded secrets: Always pull from
config.ini/.envviaconfig.get("section", {}).get("key") - Logging: Use
logging.getLogger(__name__); the logging format is configured inapp.logging_config