AppSettings : un Singleton Global pour stabiliser les modèles face aux limites du singleton LlamaIndex

, par Bertrand Degoy

Dans une architecture utilisant LlamaIndex, la gestion des modèles (LLM, embeddings, tokenizer, etc.) repose sur des mécanismes internes du framework.

Ces mécanismes fonctionnent correctement dans des cas simples, mais présentent plusieurs limites structurelles dans un écosystème d’applications devant partager des modèles identiques.

Le “singleton” interne de LlamaIndex n’est ni strict, ni stable, et peut être réinitialisé ou modifié de manière implicite selon l’ordre des imports, les contextes d’exécution ou les workers. Cela conduit à des comportements non déterministes, difficiles à diagnostiquer, et parfois dangereux (modèles différents selon les modules ou les workers). Pour stabiliser l’ensemble, il est nécessaire d’introduire un singleton global explicite, contrôlé, verrouillé, traçable et idempotent : c’est le rôle de AppSettings et AppSettingsManager qui forment un système robuste plus strict et plus fiable que celui fourni par LlamaIndex, et adapté à une architecture modulaire, multi‑thread et multiprocess.


Introduction : pourquoi un Singleton global dédié ?

Dans une architecture utilisant LlamaIndex, la gestion des modèles (LLM, embeddings, tokenizer, versions) repose sur des mécanismes internes du framework. Ces mécanismes fonctionnent correctement dans des cas simples, mais présentent plusieurs limites structurelles lorsqu’on construit une application :

  • modulaire,
  • multi‑thread,
  • multi‑process (ex. plusieurs workers uvicorn/gunicorn),
  • ou composée de plusieurs modules indépendants devant partager des modèles identiques.

Le “singleton” interne de LlamaIndex n’est ni strict, ni stable, et peut être réinitialisé ou modifié de manière implicite selon l’ordre des imports, les contextes d’exécution ou les workers. Cela conduit à des comportements non déterministes, difficiles à diagnostiquer, et parfois dangereux (modèles différents selon les modules ou les workers). Pour stabiliser l’ensemble, il est nécessaire d’introduire un singleton global explicite, contrôlé, verrouillé, traçable et idempotent : c’est le rôle de AppSettings et AppSettingsManager qui forment un système robuste plus strict et plus fiable que celui fourni par LlamaIndex, et adapté à une architecture modulaire, multi‑thread et multiprocess.

Ce texte documente :

  • le principe du Singleton global utilisé dans nos applications d’IA,
  • en quoi il diffère du “singleton” de LlamaIndex,
  • la structure et le rôle de AppSettings,
  • la logique et les responsabilités de AppSettingsManager,
  • les mécanismes d’initialisation, de verrouillage et de traçage.

1. Principe général

AppSettings est un singleton global destiné à contenir les objets critiques de l’application :

  • le modèle LLM,
  • le modèle d’embedding,
  • le tokenizer,
  • les versions associées.

Ce singleton est conçu pour être :

  • unique dans le processus,
  • initialisé une seule fois,
  • verrouillé après initialisation,
  • idempotent (les appels suivants ne modifient rien),
  • traçable (on sait qui a initialisé en premier).

Ce mécanisme garantit que toutes les parties de l’application utilisent les mêmes objets, sans risque de divergence ou de réinitialisation accidentelle.

2. Différence avec le “singleton” de LlamaIndex

LlamaIndex utilise un mécanisme interne d’initialisation (souvent via Settings ou des loaders) qui :

  • n’est pas global à l’application, mais local au framework,
  • peut être réinitialisé selon les contextes,
  • n’est pas conçu pour être partagé entre plusieurs modules indépendants,
  • ne fournit pas de verrou multi‑thread ou multi‑process,
  • ne trace pas l’origine de l’initialisation.

En résumé :

Aspect Notre AppSettings LlamaIndex Settings
Portée Globale à l’application Locale au framework
Initialisation Unique, verrouillée Réinitialisable
Idempotence Oui Non garanti
Traçage Oui (stack + timestamp) Non
Multi‑thread Protégé Non
Multi‑process Protégé (file lock) Non

Notre système est donc plus strict, plus robuste et plus adapté à une application multi‑modules.

3. Structure de AppSettings

AppSettings est un BaseModel Pydantic contenant les objets critiques :

llm: Optional[Any]
embed_model: Optional[Any]
tokenizer: Optional[Any]
versions: Optional[Dict[str, Any]]
locked: bool
initialized_by: Optional[str]
initialized_at: Optional[float]

Caractéristiques :

  • locked empêche toute modification après initialisation.
  • initialized_by contient la stack trace du premier appel.
  • initialized_at contient le timestamp d’initialisation.
  • extra = "forbid" empêche l’ajout d’attributs fantômes.
  • validate_assignment = True garantit un comportement fail‑closed.

4. Rôle de AppSettingsManager

AppSettingsManager est le seul point d’entrée pour modifier ou initialiser AppSettings.

Il fournit :

4.1. load_models()

  • Charge les objets critiques.
  • Capture la stack trace du premier appel.
  • Capture le timestamp.
  • Valide les objets.
  • Verrouille l’état.
  • Est idempotent : si déjà initialisé, ne fait rien.

4.2. validate()

  • Vérifie la présence de llm, embed_model, tokenizer.
  • Comporte un comportement fail‑closed.

4.3. lock()

  • Marque l’état comme immuable.

4.4. describe()

  • Retourne un dictionnaire d’introspection complet.

5. Sécurité multi‑thread et multi‑process

L’initialisation est protégée par deux verrous :

5.1. Verrou thread‑safe

_thread_lock = threading.Lock()

Empêche deux threads du même processus d’initialiser simultanément.

5.2. Verrou multi‑process (file lock)

_process_lock = FileLock("/tmp/appsettings_init.lock")

Empêche deux processus (ex. plusieurs workers uvicorn/gunicorn) d’initialiser simultanément.

Ce mécanisme garantit une initialisation unique, même dans un environnement multi‑workers.

6. Cycle de vie complet

  1. Une application appelle load_models().
  2. Le verrou multi‑process est acquis.
  3. Le verrou multi‑thread est acquis.
  4. Si locked == True → rien n’est fait.
  5. Sinon :
    • stack trace enregistrée,
    • timestamp enregistré,
    • objets chargés,
    • validation stricte,
    • verrouillage.
  6. Les appels suivants sont silencieusement ignorés.

Diagramme de séquence

7. Avantages de cette architecture

  • Initialisation unique garantie.
  • Protection contre les réinitialisations accidentelles.
  • Sécurité multi‑thread et multi‑process.
  • Traçabilité complète.
  • Idempotence.
  • Isolation claire entre :
    • le stockage global (AppSettings)
    • la logique métier (AppSettingsManager)
    • les configurateurs externes (ex. LlamaConfigurator).

8. Résumé

AppSettings et AppSettingsManager forment un système robuste permettant :

  • de centraliser les objets critiques,
  • de garantir une initialisation unique,
  • de sécuriser l’accès concurrent,
  • de tracer l’origine de l’initialisation,
  • de fournir une introspection complète.

Ce mécanisme est plus strict et plus fiable que celui fourni par LlamaIndex, et il est adapté à une architecture modulaire, multi‑thread et multiprocess.

Accès réservé : connectez vous pour en savoir plus.