Assistant médical francophone (RAG) construit 100 % en Rust :
- Back-end Actix-web + Tantivy + Candle (LLM)
- Front-end Yew (WebAssembly)
- Données FHIR (Patient, Observation, Condition, Encounter, MedicationRequest)
Cas d’usage : poser des questions cliniques en français et obtenir des réponses sourcées uniquement par le contexte FHIR (aucune hallucination volontaire).
- Ingestion FHIR en lot puis hot-sync toutes les 5 min (
_since) - Index sémantique Tantivy (+ champs cliniques : LOINC, valeur, unité, dates, patient)
- Recherche hybride BM25 → filtre patient/année → reclassement embeddings (All-MiniLM-L12-v2)
- RAG avec Candle (inférence locale, GPU CUDA si dispo) et tokenizer HuggingFace
- API JSON
POST /query - UI chat Yew/Trunk, design responsive
backend/
src/ (# fhir.rs, index.rs, rag.rs, rag_doc.rs, main.rs)
models/ (# modèles Candle: *.safetensors + tokenizer.json)
index/ (# index Tantivy)
dummy/ (# index “vide” pour warmup)
application.yaml
Cargo.toml
docker-compose.yml
frontend/
src/ (# app Yew)
index.html (# thème / styles)
trunk.toml
Cargo.toml
- Rust 1.78+ (toolchain stable)
- Node pas nécessaire (build web via
trunk) - Trunk pour Yew :
cargo install trunk wasm32-unknown-unknown:rustup target add wasm32-unknown-unknown- (Optionnel) CUDA pour accélérer Candle
- Un serveur FHIR R4/R5 accessible (par défaut :
http://localhost:8080/fhir)
FHIR_SERVERdanssrc/fhir.rs(ou via variable d’env si vous préférez)- Modèles Candle à placer dans
backend/models/:models/ model-00001-of-00003.safetensors model-00002-of-00003.safetensors model-00003-of-00003.safetensors tokenizer.jsonLe projet est configuré pour un petit Mistral-like (voir
rag.rs→MistralConfig). - Le modèle d’embeddings (All-MiniLM-L12-v2) est téléchargé automatiquement au premier run via
rust-bert.
- L’UI appelle l’API via
API_URL(env compile-time).- Dev local :
http://localhost:8081(par défaut) - Sinon : exporter à la compilation :
API_URL=http://localhost:8081 trunk serve
- Dev local :
cd backend
cargo run- Démarre l’API sur
http://127.0.0.1:8081 - Étapes au boot :
- Chargement embeddings (BGE/All-MiniLM)
- Chargement/initialisation du LLM Candle
- Ingestion initiale FHIR, mapping Patient ↔ ressources, index Tantivy
- Hot-sync toutes les 5 min
cd frontend
trunk serve --open
# ou API personnalisée :
API_URL=http://127.0.0.1:8081 trunk serve --open- Ouvre l’UI chat. Entrez une question, ex. :
Quel est l’IMC de John Doe en 2024 ?TA de Jane Smith en juillet 2023patient-123 : HbA1c la plus récente
Request
{ "query": "TA de John Doe en 2023" }Response
{ "output": "…réponse concise en français, uniquement basée sur le contexte fourni…" }Le prompt (
rag.rs) force : style concis, aucune invention, dates converties au format humain FR.
-
Index (
index.rs)- Schéma Tantivy :
id, patientId, patientName, resourceType, summary, loincCode, valueNum, unit, birthDate, date, dateMonth, dateDay, raw - Ajout massif en parallèle (
rayon), commit par lot - Recherche : BM25 (multi-champs) → filtrage patient/année → reclassement cosine embeddings
- Schéma Tantivy :
-
Ingestion FHIR (
fhir.rs)- Pagination
_count=100+ liennext - Sync incrémental via
_since(horodatageUtc)
- Pagination
-
Préparation des documents (
rag_doc.rs)- Résumés FR prêts pour le LLM
- Alias cliniques LOINC : TA, FC, IMC, HbA1c, Cholestérol…
- Extraction valeurs numériques + unité + dates (YYYY-MM / YYYY-MM-DD)
-
Génération (
rag.rs)- Budget
MAX_PROMPT_TOKENS = 512(sélection adaptative des chunks) - Candle (CPU ou CUDA) avec sampling greedy court (
MAX_GENERATION_TOKENS = 128) - Tokenizer HuggingFace (
tokenizers)
- Budget
-
Front-end (
frontend/src/main.rs)- Yew +
reqwasm(fetch API) - Chat minimal avec auto-scroll, Enter-to-send
index.html: thème glassmorphism sombre
- Yew +
Un docker-compose.yml est fourni côté backend (à adapter) :
- Service API sur
8081 - Montage
./modelset./index - Variable d’env
FHIR_SERVERsi vous souhaitez la passer par env
Le front peut être servi statiquement (NGINX) après
trunk build --release.
Le dossier backend/ contient des bundles synthétiques FHIR (synthetic_*) pour tester rapidement. Vous pouvez aussi pointer FHIR_SERVER vers un serveur HAPI/FHIR local.
- GPU détecté automatiquement (
Device::cuda_if_available(0)), sinon CPU. - Gardez les prompts courts et précis ; l’index fait l’essentiel du travail.
- Ajoutez vos propres synonymes LOINC dans
OBS_SYNONYMS. - Pour des réponses plus longues, augmentez
MAX_GENERATION_TOKENS.
- Projet de démonstration. Pour des données réelles : chiffrement au repos, contrôle d’accès, journalisation, cloisonnement réseau, et RGPD (base légale, minimisation, consentement, droits des personnes).
- Aucune décision médicale ne doit être prise uniquement sur la base de ce prototype.
- Le téléchargement des embeddings est lent → pré-téléchargez le modèle rust-bert en cache.
- Pas de résultats → vérifiez
FHIR_SERVER, que les bundles contiennententry[].resource. - CUDA non détecté → vérifiez pilotes/NVIDIA, version Candle et compute capability.
- CORS → le back utilise
Cors::permissive()(dev). Durcissez en prod.
# Backend
cd backend
RUST_LOG=debug cargo run
# Frontend (dev)
cd frontend
API_URL=http://127.0.0.1:8081 trunk serve --open
# Frontend (build prod)
trunk build --releaseMIT (sauf modèles tiers soumis à leurs licences respectives).
- Stack Rust : Actix-web, Tantivy, Candle, tokenizers, Yew, rust-bert
- Données : FHIR (HL7®)
- Design UI : thème custom OrbitNet dans
frontend/index.html