Architecture
RAG Pipeline
The assistant processes user queries through a Retrieval-Augmented Generation pipeline:
- Upload — files are uploaded via
POST /upload, parsed, and chunked. Images are captioned via the vision model. - Index — chunks are embedded and stored in Qdrant as vector documents
- Chat — at query time, relevant chunks are retrieved and injected into the OpenAI prompt; the LLM autonomously calls skill tools to fetch live data
- Stream — the response is streamed back to the client via SSE
All of it shares one Qdrant collection: curated training material, administrator-shared files, and every member's own uploads. What separates them is metadata that the retrieval query does not currently read — see the companion's knowledge before changing anything on this path.
Component Overview
| Component | Module | Role |
|---|---|---|
| FastAPI application | main.py |
HTTP server (create_app factory), route registration, lifespan hooks |
| RAG pipeline | rag.py |
LlamaIndex index, retrieval, query engine |
| Auth | auth.py |
JWT verification via trusted headers or JWKS |
| History | history.py |
Conversation and message persistence |
| Uploads | uploads.py |
File storage, attachment management |
| OpenAI streaming | openai_stream.py |
SSE token streaming with agentic tool-calling loop |
| Vision | openai_vision.py |
Image captioning via OpenAI vision model |
| Document processing | document_processing.py |
MIME detection and text extraction for uploads |
| Skills | skills/ |
Modular skill system: Digital Twin, Weather, Flexibility, REC Registry, Documents |
| Suggestions | suggestions.py |
Localized prompt suggestions and tool labels |
| Training materials | training_materials.py |
Git clone/checkout and incremental indexing of the Markdown corpus |
| Qdrant setup | qdrant_setup.py |
Collection initialization and management |
| Settings | settings.py |
Pydantic-settings environment configuration |
Modules that are not part of the pipeline
Present in src/, reachable from nothing, and easy to mistake for the supported path:
| Module | What it looks like | What it is |
|---|---|---|
ingest.py |
the way to index an uploaded file | uncalled. Uploads are indexed by routes._process_upload |
site_docs.py |
a second corpus ingester | a near-exact copy of training_materials.py |
training_materials_sync.py |
the sync entry point | an older one; the routes import training_materials.py's |
src/celine/assistant/skills/datasets.py |
a dataset-query skill | deliberately disabled — dataset-api needs a service token, and this service holds only the caller's (ADR-0005) |
Deleting them is owed work, not a decision to re-take.
Frontend
This repository is a pure API backend. The chat UI is maintained separately in celine-frontend:
apps/assistant— standalone full-page assistant app
The frontend communicates with this API at apiBaseUrl.
Service Dependencies
| Service | Purpose |
|---|---|
| OpenAI | Chat completions, text embeddings, and vision (image captioning) |
| Qdrant | Vector storage and similarity search |
| PostgreSQL | Conversation history, attachment metadata |
| Digital Twin | Energy data, weather, and forecast queries via skills |
| REC Registry | Membership, assets, and delivery point queries via skills |
| Flexibility API | Load-shift suggestions and gamification via skills |
| Keycloak / oauth2_proxy | JWT authentication |
Data Flow
Upload path:
POST /upload -> parse file -> (if image: caption via vision model) -> split into chunks -> embed (OpenAI) -> upsert (Qdrant) -> store metadata (PostgreSQL)
Chat path:
POST /chat -> verify JWT -> load history -> load authorized attachments -> retrieve context (Qdrant) -> build prompt -> stream (OpenAI SSE with agentic tool-calling loop)
Database Models
PostgreSQL (async via SQLAlchemy + asyncpg). Alembic handles migrations. Models:
Conversation— linked to a user identity from the JWT subject claimMessage— role (user/assistant), content, timestamp, optional attachment refsAttachment— file metadata: name, MIME type, Qdrant collection reference, scope (user/system)