Validé sur PC Windows (DESKTOP-58D5CAC, 2560x1600) : - 8 clics résolus visuellement (1 anchor_template, 1 som_text_match, 6 som_vlm) - Score moyen 0.75, temps moyen 1.6s - Texte tapé correctement (bonjour, test word, date, email) - 0 retries, 2 actions non vérifiées (OK) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
410 lines
14 KiB
Markdown
410 lines
14 KiB
Markdown
# Design Document - FAISS Rebuild Propre
|
|
|
|
**Auteur :** Dom, Alice Kiro - 22 décembre 2025
|
|
|
|
## Overview
|
|
|
|
Le système FAISS Rebuild Propre résout le problème de pollution d'index causé par l'accumulation de vecteurs obsolètes lors des mises à jour de prototypes. La stratégie adoptée est simple et sûre : **clear + reindex complet** depuis les derniers prototypes validés.
|
|
|
|
## Architecture
|
|
|
|
### Composants Principaux
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ FAISS Rebuild Propre │
|
|
├─────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ FAISSManager Enhanced ←→ WorkflowPipeline Enhanced │
|
|
│ ✅ clear() amélioré ✅ _extract_node_vector() │
|
|
│ ✅ reindex() nouveau ✅ _index_workflow_embeddings │
|
|
│ │
|
|
│ Test Suite ←→ Trigger Logic │
|
|
│ ✅ test_faiss_reindex.py ✅ Post-validation trigger │
|
|
│ ✅ Batch session trigger │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
## Components and Interfaces
|
|
|
|
### 1. FAISSManager Enhanced
|
|
|
|
#### Interface Améliorée
|
|
|
|
```python
|
|
class FAISSManager:
|
|
def clear(self) -> None:
|
|
"""Vider complètement l'index + reset état d'entraînement."""
|
|
|
|
def reindex(self, items: Iterable[Tuple[str, np.ndarray, dict]],
|
|
force_train_ivf: bool = True) -> int:
|
|
"""Reconstruit l'index à partir d'une source canonique."""
|
|
|
|
def _create_index(self) -> faiss.Index:
|
|
"""Créer un nouvel index selon la configuration."""
|
|
```
|
|
|
|
#### Méthode clear() Améliorée
|
|
|
|
```python
|
|
def clear(self) -> None:
|
|
"""Vider complètement l'index + reset état d'entraînement."""
|
|
self.index = self._create_index()
|
|
self.metadata_store.clear()
|
|
self.next_id = 0
|
|
|
|
# IMPORTANT: reset IVF training state
|
|
self.training_vectors.clear()
|
|
self.is_trained = (self.index_type == "Flat")
|
|
```
|
|
|
|
**Améliorations :**
|
|
- Reset complet de l'état IVF training
|
|
- Réinitialisation des training_vectors
|
|
- Gestion correcte du flag is_trained selon le type d'index
|
|
|
|
#### Méthode reindex() Nouvelle
|
|
|
|
```python
|
|
def reindex(self, items: Iterable[Tuple[str, np.ndarray, dict]],
|
|
force_train_ivf: bool = True) -> int:
|
|
"""
|
|
Reconstruit l'index à partir d'une source canonique (vecteurs).
|
|
items: Iterable[(embedding_id: str, vector: np.ndarray, metadata: dict)]
|
|
"""
|
|
self.clear()
|
|
|
|
count = 0
|
|
for embedding_id, vector, metadata in items:
|
|
if vector is None:
|
|
continue
|
|
self.add_embedding(embedding_id, vector, metadata or {})
|
|
count += 1
|
|
|
|
# Si IVF + petit volume, add_embedding ne déclenche pas forcément l'entraînement
|
|
if (self.index_type == "IVF" and force_train_ivf and
|
|
(not self.is_trained) and self.training_vectors):
|
|
self._train_ivf_index()
|
|
|
|
return count
|
|
```
|
|
|
|
**Fonctionnalités :**
|
|
- Clear complet avant reconstruction
|
|
- Ajout sécurisé avec validation des vecteurs
|
|
- Force training IVF même pour petits volumes
|
|
- Retour du nombre d'éléments indexés
|
|
|
|
### 2. WorkflowPipeline Enhanced
|
|
|
|
#### Extraction de Vecteurs Multi-Version
|
|
|
|
```python
|
|
def _extract_node_vector(self, node) -> Optional[np.ndarray]:
|
|
"""
|
|
Récupérer le prototype vecteur d'un node, compatible avec plusieurs versions de modèle.
|
|
Retourne np.ndarray ou None.
|
|
"""
|
|
import numpy as np
|
|
|
|
# v1: prototype stocké en liste directement
|
|
tpl = getattr(node, "template", None)
|
|
if tpl is not None:
|
|
proto_list = getattr(tpl, "embedding_prototype", None)
|
|
if isinstance(proto_list, list):
|
|
v = np.array(proto_list, dtype=np.float32)
|
|
return v
|
|
|
|
# v2: prototype stocké sur disque via EmbeddingPrototype.vector_id
|
|
emb = getattr(tpl, "embedding", None)
|
|
if emb is not None:
|
|
vector_id = getattr(emb, "vector_id", None)
|
|
if vector_id:
|
|
try:
|
|
return np.load(vector_id).astype(np.float32)
|
|
except Exception:
|
|
return None
|
|
|
|
# fallback (ancienne nomenclature)
|
|
st = getattr(node, "screen_template", None)
|
|
if st is not None:
|
|
p = getattr(st, "embedding_prototype_path", None)
|
|
if p:
|
|
try:
|
|
return np.load(p).astype(np.float32)
|
|
except Exception:
|
|
return None
|
|
|
|
return None
|
|
```
|
|
|
|
**Support Multi-Version :**
|
|
- v1: embedding_prototype en liste directe
|
|
- v2: embedding.vector_id avec fichier sur disque
|
|
- Fallback: screen_template legacy
|
|
- Gestion robuste des erreurs
|
|
|
|
#### Indexation Workflow Améliorée
|
|
|
|
```python
|
|
def _index_workflow_embeddings(self, workflow: Workflow) -> None:
|
|
"""
|
|
Indexer les embeddings des nodes dans FAISS (rebuild propre).
|
|
"""
|
|
if not self.faiss_manager:
|
|
return
|
|
|
|
items = []
|
|
for node in workflow.nodes:
|
|
vec = self._extract_node_vector(node)
|
|
if vec is None:
|
|
continue
|
|
items.append((
|
|
node.node_id,
|
|
vec,
|
|
{
|
|
"workflow_id": workflow.workflow_id,
|
|
"node_id": node.node_id,
|
|
"node_name": getattr(node, "name", "")
|
|
}
|
|
))
|
|
|
|
n = self.faiss_manager.reindex(items, force_train_ivf=True)
|
|
logger.info(f"FAISS reindexed: {n} node prototypes (workflow={workflow.workflow_id})")
|
|
```
|
|
|
|
**Avantages :**
|
|
- Construction de liste canonique avant reindex
|
|
- Métadonnées enrichies (workflow_id, node_id, node_name)
|
|
- Force training IVF pour cohérence
|
|
- Logging informatif
|
|
|
|
## Data Models
|
|
|
|
### Item de Reindex
|
|
|
|
```python
|
|
@dataclass
|
|
class ReindexItem:
|
|
embedding_id: str
|
|
vector: np.ndarray
|
|
metadata: Dict[str, Any]
|
|
|
|
def __post_init__(self):
|
|
if self.vector is not None:
|
|
self.vector = self.vector.astype(np.float32)
|
|
```
|
|
|
|
### Résultat de Reindex
|
|
|
|
```python
|
|
@dataclass
|
|
class ReindexResult:
|
|
success: bool
|
|
items_processed: int
|
|
items_skipped: int
|
|
training_performed: bool
|
|
duration_ms: float
|
|
error_message: Optional[str] = None
|
|
```
|
|
|
|
## Correctness Properties
|
|
|
|
*Une propriété est une caractéristique ou un comportement qui doit être vrai pour toutes les exécutions valides d'un système - essentiellement, une déclaration formelle sur ce que le système devrait faire.*
|
|
|
|
### Property 1: Clear Operation Completeness
|
|
*Pour tout* FAISSManager, après un appel à clear(), l'index doit être vide (ntotal=0), les métadonnées vides, next_id remis à 0, training_vectors vidé, et is_trained défini selon le type d'index
|
|
**Validates: Requirements 1.2, 1.3, 1.4, 1.5**
|
|
|
|
### Property 2: Embedding Removal Completeness
|
|
*Pour tout* embedding ajouté puis supprimé via remove_embedding(), ni les métadonnées ni le vecteur ne doivent être présents dans l'index
|
|
**Validates: Requirements 1.1**
|
|
|
|
### Property 3: Reindex Consistency
|
|
*Pour tout* ensemble d'items valides, reindex() doit produire un index contenant exactement ces items, sans doublons ni éléments manquants, et retourner le bon nombre d'items traités
|
|
**Validates: Requirements 2.1, 2.2, 2.4**
|
|
|
|
### Property 4: Invalid Vector Handling
|
|
*Pour tout* ensemble d'items contenant des vecteurs invalides (None), reindex() doit les ignorer et continuer le traitement des vecteurs valides
|
|
**Validates: Requirements 2.5**
|
|
|
|
### Property 5: IVF Training Consistency
|
|
*Pour tout* reindex avec force_train_ivf=True sur un index IVF contenant des vecteurs, l'index résultant doit être dans l'état is_trained=True
|
|
**Validates: Requirements 2.3**
|
|
|
|
### Property 6: Vector Extraction Multi-Format
|
|
*Pour tout* node contenant un vecteur prototype dans n'importe quel format supporté (template.embedding_prototype, embedding.vector_id, ou screen_template legacy), _extract_node_vector() doit retourner un np.ndarray de type float32
|
|
**Validates: Requirements 3.1, 3.2, 3.3, 3.4**
|
|
|
|
### Property 7: Vector Extraction Graceful Failure
|
|
*Pour tout* node sans vecteur prototype valide, _extract_node_vector() doit retourner None sans lever d'exception
|
|
**Validates: Requirements 3.5**
|
|
|
|
### Property 8: Workflow Indexing Completeness
|
|
*Pour tout* workflow contenant des nodes avec vecteurs valides, _index_workflow_embeddings() doit extraire tous les vecteurs valides et les indexer avec les métadonnées appropriées (workflow_id, node_id)
|
|
**Validates: Requirements 4.1, 4.2**
|
|
|
|
### Property 9: IVF Training Force in Pipeline
|
|
*Pour tout* workflow indexé avec un FAISSManager IVF, le système doit forcer l'entraînement IVF pour assurer la cohérence
|
|
**Validates: Requirements 4.3**
|
|
|
|
### Property 10: Trigger Logic Validation
|
|
*Pour tout* prototype mis à jour avec post-conditions OK, le système doit déclencher un rebuild FAISS
|
|
**Validates: Requirements 5.1**
|
|
|
|
### Property 11: Batch Session Trigger
|
|
*Pour toute* session d'apprentissage par batch qui se termine, le système doit déclencher un rebuild FAISS
|
|
**Validates: Requirements 5.2**
|
|
|
|
### Property 12: Frame Update Restraint
|
|
*Pour toute* série de mises à jour de frames, le système ne doit pas déclencher de rebuild à chaque frame
|
|
**Validates: Requirements 5.3**
|
|
|
|
### Property 13: Rebuild Failure Resilience
|
|
*Pour tout* échec de rebuild, le système doit maintenir l'index existant dans un état valide sans corruption
|
|
**Validates: Requirements 5.5**
|
|
|
|
### Property 14: Flat Index Reindex Cleanup
|
|
*Pour tout* index Flat, après reindex avec de nouveaux items, les anciens items doivent être complètement supprimés
|
|
**Validates: Requirements 6.1**
|
|
|
|
### Property 15: IVF Small Dataset Training
|
|
*Pour tout* index IVF, même avec un petit dataset, reindex() avec force_train_ivf=True doit déclencher l'entraînement
|
|
**Validates: Requirements 6.2**
|
|
|
|
### Property 16: Metadata Consistency After Reindex
|
|
*Pour tout* reindex, les métadonnées de chaque item doivent être préservées et cohérentes après l'opération
|
|
**Validates: Requirements 6.3**
|
|
|
|
### Property 17: Search Accuracy After Reindex
|
|
*Pour tout* reindex suivi d'une recherche, les résultats doivent être corrects et correspondre aux vecteurs indexés
|
|
**Validates: Requirements 6.4**
|
|
|
|
### Property 18: Index State Validation
|
|
*Pour tout* reindex, l'état de l'index (ntotal, is_trained) doit être cohérent avec le contenu effectivement indexé
|
|
**Validates: Requirements 6.5**
|
|
|
|
## Error Handling
|
|
|
|
### Stratégies de Gestion d'Erreurs
|
|
|
|
1. **Vector Loading Errors**
|
|
- Try/catch sur np.load() avec fallback gracieux
|
|
- Log warning pour vecteurs non chargeables
|
|
- Continue processing avec autres vecteurs
|
|
|
|
2. **FAISS Training Errors**
|
|
- Validation du nombre minimum de vecteurs pour IVF
|
|
- Retry avec paramètres ajustés si échec
|
|
- Fallback vers index Flat si IVF impossible
|
|
|
|
3. **Memory Errors**
|
|
- Monitoring de l'utilisation mémoire pendant reindex
|
|
- Batch processing pour gros volumes
|
|
- Cleanup automatique en cas d'échec
|
|
|
|
### Logging Strategy
|
|
|
|
```python
|
|
# Niveaux de logging
|
|
logger.info(f"FAISS reindex started: {len(items)} items")
|
|
logger.debug(f"Processing item {embedding_id}: vector shape {vector.shape}")
|
|
logger.warning(f"Skipped invalid vector for {embedding_id}")
|
|
logger.error(f"FAISS reindex failed: {error}")
|
|
logger.info(f"FAISS reindex completed: {count} items in {duration}ms")
|
|
```
|
|
|
|
## Testing Strategy
|
|
|
|
### Tests Unitaires
|
|
|
|
1. **test_faiss_clear_resets_state**
|
|
- Valide reset complet après clear()
|
|
- Vérifie état IVF training
|
|
|
|
2. **test_faiss_reindex_flat_removes_old_entries**
|
|
- Valide suppression des anciens vecteurs
|
|
- Vérifie cohérence métadonnées
|
|
|
|
3. **test_faiss_reindex_ivf_trains_even_small**
|
|
- Valide training forcé sur petits datasets
|
|
- Vérifie état is_trained
|
|
|
|
4. **test_extract_node_vector_multi_version**
|
|
- Teste support des différents formats
|
|
- Valide fallback gracieux
|
|
|
|
### Tests d'Intégration
|
|
|
|
1. **test_workflow_pipeline_reindex_integration**
|
|
- Test complet pipeline → FAISS
|
|
- Validation end-to-end
|
|
|
|
2. **test_reindex_performance_large_dataset**
|
|
- Test performance sur gros volumes
|
|
- Validation mémoire
|
|
|
|
### Tests de Propriétés
|
|
|
|
Chaque propriété de correctness sera implémentée comme un test property-based avec minimum 100 itérations.
|
|
|
|
## Trigger Logic
|
|
|
|
### Moments de Déclenchement
|
|
|
|
1. **Post-Validation Trigger**
|
|
```python
|
|
# Après mise à jour de prototype validée
|
|
if post_conditions_ok and prototype_updated:
|
|
self.trigger_faiss_rebuild()
|
|
```
|
|
|
|
2. **Batch Session Trigger**
|
|
```python
|
|
# À la fin d'une session d'apprentissage
|
|
def end_learning_session(self):
|
|
self.trigger_faiss_rebuild()
|
|
self.save_session_metrics()
|
|
```
|
|
|
|
3. **Manual Trigger**
|
|
```python
|
|
# Commande administrative
|
|
def admin_rebuild_faiss(self):
|
|
return self.faiss_manager.reindex(self.get_all_prototypes())
|
|
```
|
|
|
|
### Éviter les Triggers Excessifs
|
|
|
|
- **PAS** de rebuild à chaque frame
|
|
- **PAS** de rebuild sur échecs temporaires
|
|
- **OUI** rebuild après validation réussie
|
|
- **OUI** rebuild en fin de batch
|
|
|
|
## Performance Considerations
|
|
|
|
### Optimisations
|
|
|
|
1. **Batch Processing**
|
|
- Grouper les updates avant rebuild
|
|
- Éviter les rebuilds fréquents
|
|
|
|
2. **Memory Management**
|
|
- Clear explicite des anciens vecteurs
|
|
- Monitoring utilisation mémoire
|
|
|
|
3. **IVF Training**
|
|
- Force training même petits volumes
|
|
- Paramètres optimisés par défaut
|
|
|
|
### Métriques
|
|
|
|
- Temps de rebuild par nombre de vecteurs
|
|
- Utilisation mémoire pendant rebuild
|
|
- Taux de réussite des training IVF
|
|
- Fréquence des rebuilds déclenchés
|
|
|
|
## Conclusion
|
|
|
|
Le système FAISS Rebuild Propre garantit la cohérence et la propreté de l'index FAISS en éliminant la pollution par vecteurs obsolètes. L'approche "clear + reindex complet" est simple, sûre et efficace pour maintenir la qualité de l'apprentissage du système RPA Vision V3. |