docuresume-backend / API_LOCAL_MODEL.md
K2MAR's picture
Add T5 model integration for real summary generation
c43736a

📱 API pour Modèle T5 Local sur Téléphone

🎯 Architecture

┌─────────────────────────────────────────────────┐
│              Application Mobile                 │
│  ┌──────────────────────────────────────────┐   │
│  │  Modèle T5-Small Finetuned (ONNX)       │   │
│  │  • 60 MB quantifié INT8                 │   │
│  │  • Génération de résumés locale         │   │
│  └──────────────────────────────────────────┘   │
│                     ↕                           │
│        API Calls (données uniquement)          │
└─────────────────────────────────────────────────┘
                     ↕
┌─────────────────────────────────────────────────┐
│    Backend API (Hugging Face Spaces)            │
│  • Stockage des documents (Supabase)           │
│  • Fournit le contenu textuel extrait          │
│  • Reçoit et stocke les résumés générés        │
└─────────────────────────────────────────────────┘

📡 Nouvelles API Ajoutées

1️⃣ GET /documents/{document_id}/content

Récupérer le contenu textuel d'un document pour génération locale

Requête

curl https://k2mar-docuresume-backend.hf.space/documents/DOC_ID/content

Réponse

{
  "document_id": "uuid-here",
  "filename": "Mon Document.pdf",
  "content": "Contenu textuel extrait du PDF avec OCR...",
  "page_count": 5,
  "file_size": 245678,
  "content_length": 2847
}

Utilisation Mobile

// Flutter
Future<String> getDocumentContent(String documentId) async {
  final response = await http.get(
    Uri.parse('$baseUrl/documents/$documentId/content')
  );
  
  if (response.statusCode == 200) {
    final data = json.decode(response.body);
    return data['content']; // Texte à résumer
  }
  throw Exception('Failed to load content');
}

2️⃣ GET /documents/user/{user_id}/contents

Récupérer le contenu de tous les documents d'un utilisateur

Requête

curl "https://k2mar-docuresume-backend.hf.space/documents/user/USER_ID/contents?limit=100&include_content=true"

Paramètres

  • limit: nombre max de documents (défaut: 100)
  • include_content: inclure le contenu texte (défaut: true)

Réponse

{
  "user_id": "user-uuid",
  "documents": [
    {
      "document_id": "doc-uuid-1",
      "filename": "Document1.pdf",
      "page_count": 5,
      "file_size": 245678,
      "uploaded_at": "2026-01-27T00:00:00",
      "processing_status": "completed",
      "content": "Texte du document 1...",
      "content_length": 2847
    },
    {
      "document_id": "doc-uuid-2",
      "filename": "Document2.pdf",
      "content": "Texte du document 2...",
      "content_length": 4521
    }
  ],
  "count": 2,
  "total_content_length": 7368
}

Utilisation Mobile

// Flutter - Télécharger tous les documents pour résumé batch
Future<List<Document>> getAllDocumentsContents(String userId) async {
  final response = await http.get(
    Uri.parse('$baseUrl/documents/user/$userId/contents?include_content=true')
  );
  
  if (response.statusCode == 200) {
    final data = json.decode(response.body);
    return (data['documents'] as List)
        .map((doc) => Document.fromJson(doc))
        .toList();
  }
  throw Exception('Failed to load documents');
}

3️⃣ POST /documents/{document_id}/summaries

Envoyer un résumé généré localement vers le serveur

Requête

curl -X POST https://k2mar-docuresume-backend.hf.space/documents/DOC_ID/summaries \
  -H "Content-Type: application/json" \
  -d '{
    "summary_type": "abstractive",
    "summary_level": "medium",
    "content": "Résumé généré par T5-small...",
    "model_name": "t5-small-finetuned",
    "generation_time": 3.45,
    "token_count": 150,
    "rouge_1_score": 0.87,
    "rouge_2_score": 0.73,
    "rouge_l_score": 0.81
  }'

Champs

  • content (requis): texte du résumé généré
  • summary_type: "abstractive" | "extractive" | "hybrid" (défaut: "extractive")
  • summary_level: "short" | "medium" | "long" (défaut: "medium")
  • model_name: nom du modèle (défaut: "t5-small-finetuned")
  • generation_time: temps de génération en secondes (optionnel)
  • token_count: nombre de tokens générés (optionnel)
  • rouge_1_score, rouge_2_score, rouge_l_score: métriques ROUGE (optionnel)

Réponse

{
  "message": "Résumé créé avec succès",
  "summary_id": "summary-uuid",
  "document_id": "doc-uuid",
  "summary_type": "abstractive",
  "created_at": "2026-01-27T02:30:00"
}

Utilisation Mobile

// Flutter - Envoyer le résumé généré
Future<void> submitSummary(String documentId, String summaryText, double generationTime) async {
  final response = await http.post(
    Uri.parse('$baseUrl/documents/$documentId/summaries'),
    headers: {'Content-Type': 'application/json'},
    body: json.encode({
      'summary_type': 'abstractive',
      'summary_level': 'medium',
      'content': summaryText,
      'model_name': 't5-small-finetuned',
      'generation_time': generationTime,
      'token_count': summaryText.split(' ').length
    })
  );
  
  if (response.statusCode != 200) {
    throw Exception('Failed to submit summary');
  }
}

4️⃣ POST /summaries/batch

Créer plusieurs résumés en batch (génération locale de plusieurs documents)

Requête

curl -X POST https://k2mar-docuresume-backend.hf.space/summaries/batch \
  -H "Content-Type: application/json" \
  -d '[
    {
      "document_id": "doc-uuid-1",
      "content": "Résumé du document 1...",
      "summary_type": "abstractive",
      "generation_time": 2.5
    },
    {
      "document_id": "doc-uuid-2",
      "content": "Résumé du document 2...",
      "summary_type": "abstractive",
      "generation_time": 3.1
    }
  ]'

Réponse

{
  "message": "2 résumés créés avec succès",
  "count": 2,
  "summary_ids": ["summary-uuid-1", "summary-uuid-2"]
}

Utilisation Mobile

// Flutter - Soumettre plusieurs résumés d'un coup
Future<void> submitBatchSummaries(List<Summary> summaries) async {
  final payload = summaries.map((s) => {
    'document_id': s.documentId,
    'content': s.content,
    'summary_type': 'abstractive',
    'summary_level': 'medium',
    'model_name': 't5-small-finetuned',
    'generation_time': s.generationTime
  }).toList();
  
  final response = await http.post(
    Uri.parse('$baseUrl/summaries/batch'),
    headers: {'Content-Type': 'application/json'},
    body: json.encode(payload)
  );
  
  if (response.statusCode != 200) {
    throw Exception('Failed to submit batch');
  }
}

5️⃣ GET /summaries/user/{user_id}

Récupérer tous les résumés générés par un utilisateur

Requête

curl "https://k2mar-docuresume-backend.hf.space/summaries/user/USER_ID?limit=50&summary_type=abstractive"

Paramètres

  • limit: nombre max de résumés (défaut: 50)
  • summary_type: filtrer par type (optionnel)

Réponse

{
  "user_id": "user-uuid",
  "summaries": [
    {
      "id": "summary-uuid",
      "document_id": "doc-uuid",
      "content": "Résumé...",
      "summary_type": "abstractive",
      "summary_level": "medium",
      "model_name": "t5-small-finetuned",
      "generation_time": 3.45,
      "created_at": "2026-01-27T02:30:00",
      "documents": {
        "original_filename": "Document.pdf"
      }
    }
  ],
  "count": 1
}

🔄 Workflow Complet

Scénario 1: Résumé d'un document

// 1. Récupérer le contenu du document
String content = await getDocumentContent(documentId);

// 2. Générer le résumé avec le modèle T5 local
String summary = await t5Model.generateSummary(content);

// 3. Envoyer le résumé au serveur
await submitSummary(documentId, summary, generationTime);

Scénario 2: Résumé de tous les documents

// 1. Récupérer tous les documents avec contenus
List<Document> docs = await getAllDocumentsContents(userId);

// 2. Générer les résumés en local (batch ou séquentiel)
List<Summary> summaries = [];
for (var doc in docs) {
  String summary = await t5Model.generateSummary(doc.content);
  summaries.add(Summary(
    documentId: doc.id,
    content: summary,
    generationTime: ...
  ));
}

// 3. Envoyer tous les résumés d'un coup
await submitBatchSummaries(summaries);

📊 Intégration du Modèle T5 sur Mobile

Structure du package embarqué

assets/
  models/
    t5-small-finetuned-logits-int8.onnx  (60 MB)
    tokenizer/
      config.json
      tokenizer.json
      tokenizer_config.json

Exemple Flutter avec ONNX Runtime

import 'package:onnxruntime/onnxruntime.dart';

class T5SummaryModel {
  late OrtSession session;
  late Tokenizer tokenizer;
  
  Future<void> loadModel() async {
    // Charger le modèle ONNX
    final modelPath = 'assets/models/t5-small-finetuned-logits-int8.onnx';
    session = await OrtSession.fromAsset(modelPath);
    
    // Charger le tokenizer
    tokenizer = Tokenizer.fromAsset('assets/models/tokenizer');
  }
  
  Future<String> generateSummary(String text, {int maxTokens = 150}) async {
    final startTime = DateTime.now();
    
    // 1. Tokeniser l'entrée
    final inputIds = tokenizer.encode("summarize: $text", maxLength: 512);
    
    // 2. Boucle autoregressive
    List<int> outputIds = [tokenizer.padTokenId];
    
    for (int i = 0; i < maxTokens; i++) {
      // Forward pass ONNX
      final inputs = {
        'input_ids': inputIds,
        'attention_mask': List.filled(inputIds.length, 1),
        'decoder_input_ids': outputIds
      };
      
      final outputs = await session.run(inputs);
      final logits = outputs[0];
      
      // Argmax sur le dernier token
      final nextToken = argmax(logits[-1]);
      
      if (nextToken == tokenizer.eosTokenId) break;
      outputIds.add(nextToken);
    }
    
    // 3. Décoder la sortie
    final summary = tokenizer.decode(outputIds, skipSpecialTokens: true);
    final duration = DateTime.now().difference(startTime);
    
    print('Génération: ${duration.inSeconds}s');
    return summary;
  }
}

⚙️ Configuration Recommandée

Contraintes minimales

  • RAM disponible: 1 GB libre (2 GB recommandé)
  • Stockage: 100 MB (modèle + cache)
  • Android: API 24+ (Android 7.0)
  • iOS: iOS 12+

Optimisations

  1. Quantification INT8: déjà appliquée (60 MB)
  2. Chunking: découper les longs documents (>2000 tokens)
  3. Cache: stocker les résumés localement
  4. Background processing: générer en arrière-plan
  5. Batch processing: traiter plusieurs docs pendant la nuit

🧪 Test des Nouvelles API

# Test 1: Récupérer contenu d'un document
curl https://k2mar-docuresume-backend.hf.space/documents/DOC_ID/content | jq '.content_length'

# Test 2: Récupérer tous les contenus
curl "https://k2mar-docuresume-backend.hf.space/documents/user/USER_ID/contents?include_content=false" | jq '.count'

# Test 3: Soumettre un résumé test
curl -X POST https://k2mar-docuresume-backend.hf.space/documents/DOC_ID/summaries \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Ceci est un résumé test généré par T5-small",
    "summary_type": "abstractive",
    "model_name": "t5-small-finetuned",
    "generation_time": 2.5
  }' | jq

# Test 4: Récupérer résumés d'un user
curl https://k2mar-docuresume-backend.hf.space/summaries/user/USER_ID | jq '.count'

📈 Métriques à Tracker

Pour mesurer la performance du modèle local:

class SummaryMetrics {
  double generationTime;      // Temps de génération (secondes)
  int inputTokens;            // Nombre de tokens en entrée
  int outputTokens;           // Nombre de tokens générés
  double batteryUsed;         // Batterie consommée (%)
  double memoryPeak;          // RAM max utilisée (MB)
  
  // Optionnel: scores ROUGE si vous avez un résumé de référence
  double? rouge1;
  double? rouge2;
  double? rougeL;
}

Envoyez ces métriques au serveur lors de la soumission du résumé pour analyse.


🚀 Déploiement

Les nouvelles API sont déjà déployées sur:

https://k2mar-docuresume-backend.hf.space

✅ Commit: ba0c701
✅ Message: "Add APIs for local T5 model: content retrieval and summary submission"
✅ Status: En ligne sur Hugging Face Spaces

Vous pouvez maintenant intégrer le modèle T5 dans votre app mobile! 📱