- Add 'summary' field to Chunk collection (vectorized with text2vec) - Migrate from Dynamic index to HNSW + RQ for both Chunk and Summary - Add LLM summarizer module (utils/llm_summarizer.py) - Add migration scripts (migrate_add_summary.py, restore_*.py) - Add summary generation utilities and progress tracking - Add testing and cleaning tools (outils_test_and_cleaning/) - Add comprehensive documentation (ANALYSE_*.md, guides) - Remove obsolete files (linear_config.py, old test files) - Update .gitignore to exclude backups and temp files 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
740 lines
22 KiB
Markdown
740 lines
22 KiB
Markdown
# Lien entre Summary et Chunk - Explication Complète
|
|
|
|
**Date**: 2026-01-03
|
|
**Fichiers analysés**: `utils/weaviate_ingest.py`, `schema.py`, `pdf_pipeline.py`
|
|
|
|
---
|
|
|
|
## 📋 Table des Matières
|
|
|
|
1. [Vue d'Ensemble](#1-vue-densemble)
|
|
2. [Lien Théorique entre Summary et Chunk](#2-lien-théorique-entre-summary-et-chunk)
|
|
3. [Comment les Summary sont Créés](#3-comment-les-summary-sont-créés)
|
|
4. [Pourquoi les Summary sont Vides](#4-pourquoi-les-summary-sont-vides)
|
|
5. [Comment Corriger le Problème](#5-comment-corriger-le-problème)
|
|
|
|
---
|
|
|
|
## 1. Vue d'Ensemble
|
|
|
|
### Architecture Hiérarchique
|
|
|
|
```
|
|
Document (ex: Peirce Collected Papers)
|
|
│
|
|
├─► TOC (Table des Matières)
|
|
│ └─ Structure hiérarchique des sections
|
|
│
|
|
├─► Summary (8,425 objets) - MACRO
|
|
│ └─ Un résumé pour chaque section de la TOC
|
|
│ └─ Vectorisé pour recherche sémantique chapitres
|
|
│
|
|
└─► Chunk (5,404 objets) - MICRO
|
|
└─ Fragments de texte (200-800 chars)
|
|
└─ Vectorisé pour recherche sémantique fine
|
|
```
|
|
|
|
### Lien entre Summary et Chunk
|
|
|
|
Le lien devrait être **par sectionPath** :
|
|
|
|
```python
|
|
Summary:
|
|
sectionPath: "Peirce: CP 5.314 > La sémiose et les catégories"
|
|
chunksCount: 23 # ← Nombre de chunks dans cette section
|
|
text: "Ce passage explore la théorie de la sémiose..."
|
|
|
|
Chunk 1:
|
|
sectionPath: "Peirce: CP 5.314 > La sémiose et les catégories"
|
|
text: "Un signe, ou representamen, est quelque chose..."
|
|
|
|
Chunk 2:
|
|
sectionPath: "Peirce: CP 5.314 > La sémiose et les catégories"
|
|
text: "La sémiose est l'action du signe..."
|
|
|
|
... (21 autres chunks)
|
|
|
|
Chunk 23:
|
|
sectionPath: "Peirce: CP 5.314 > La sémiose et les catégories"
|
|
text: "Ainsi la relation triadique est irréductible..."
|
|
```
|
|
|
|
**Principe**: Tous les Chunks avec le même `sectionPath` appartiennent au Summary correspondant.
|
|
|
|
---
|
|
|
|
## 2. Lien Théorique entre Summary et Chunk
|
|
|
|
### 2.1 Modèle de Données
|
|
|
|
#### Summary (Résumé de Section)
|
|
|
|
**Fichier**: `utils/weaviate_ingest.py:86-100`
|
|
|
|
```python
|
|
class SummaryObject(TypedDict):
|
|
"""Structure d'un Summary dans Weaviate."""
|
|
|
|
sectionPath: str # "Peirce: CP 5.314 > La sémiose"
|
|
title: str # "La sémiose et les catégories"
|
|
level: int # 2 (profondeur hiérarchique)
|
|
text: str # "Ce passage explore..." (RÉSUMÉ LLM)
|
|
concepts: List[str] # ["sémiose", "triade", "signe"]
|
|
chunksCount: int # 23 (nombre de chunks dans cette section)
|
|
document: {
|
|
sourceId: str # "peirce_collected_papers_fixed"
|
|
}
|
|
```
|
|
|
|
**Champs vectorisés**:
|
|
- ✅ `text` → Vectorisé avec BGE-M3 (1024-dim)
|
|
- ✅ `concepts` → Vectorisé avec BGE-M3
|
|
|
|
**Champs de filtrage**:
|
|
- `sectionPath` → Pour lier avec Chunks
|
|
- `level` → Pour hiérarchie (1=chapitre, 2=section, 3=subsection)
|
|
- `chunksCount` → Pour navigation
|
|
|
|
#### Chunk (Fragment de Texte)
|
|
|
|
**Fichier**: `schema.py:216-280`
|
|
|
|
```python
|
|
{
|
|
"text": str, # Contenu du fragment (200-800 chars)
|
|
"keywords": List[str], # ["sémiose", "triade"]
|
|
|
|
"sectionPath": str, # "Peirce: CP 5.314 > La sémiose" (LIEN AVEC SUMMARY)
|
|
"sectionLevel": int, # 2
|
|
"chapterTitle": str, # "La sémiose et les catégories"
|
|
"orderIndex": int, # 42 (position dans le document)
|
|
"unitType": str, # "argument", "définition", etc.
|
|
|
|
"work": {
|
|
"title": str, # "Collected Papers"
|
|
"author": str, # "Peirce"
|
|
},
|
|
"document": {
|
|
"sourceId": str, # "peirce_collected_papers_fixed"
|
|
"edition": str, # "Hartshorne & Weiss"
|
|
}
|
|
}
|
|
```
|
|
|
|
### 2.2 Comment le Lien Fonctionne
|
|
|
|
**Lien par sectionPath** (chaîne de caractères):
|
|
|
|
```python
|
|
# Recherche dans Summary
|
|
summary_result = summaries.query.near_text(query="sémiose", limit=3)
|
|
top_section = summary_result.objects[0].properties['sectionPath']
|
|
# → "Peirce: CP 5.314 > La sémiose et les catégories"
|
|
|
|
# Récupérer tous les Chunks de cette section
|
|
chunks = client.collections.get("Chunk")
|
|
chunk_result = chunks.query.fetch_objects(
|
|
filters=Filter.by_property("sectionPath").like(f"{top_section}*"),
|
|
limit=100
|
|
)
|
|
# → Retourne les 23 chunks appartenant à cette section
|
|
```
|
|
|
|
**Avantages de ce design** (vs cross-references):
|
|
- ✅ Pas besoin de UUID references
|
|
- ✅ Requête unique (pas de jointures)
|
|
- ✅ Filtrage simple avec LIKE ou EQUAL
|
|
- ✅ Lisible et debuggable
|
|
|
|
**Inconvénients**:
|
|
- ⚠️ Sensible aux typos dans sectionPath
|
|
- ⚠️ Pas de validation d'intégrité référentielle
|
|
|
|
---
|
|
|
|
## 3. Comment les Summary sont Créés
|
|
|
|
### 3.1 Fonction d'Ingestion
|
|
|
|
**Fichier**: `utils/weaviate_ingest.py:632-731`
|
|
|
|
```python
|
|
def ingest_summaries(
|
|
client: WeaviateClient,
|
|
doc_name: str,
|
|
toc: List[Dict[str, Any]], # Table des matières
|
|
summaries_content: Dict[str, str], # ← RÉSUMÉS LLM (actuellement vide !)
|
|
) -> int:
|
|
"""Insert section summaries into the Summary collection."""
|
|
|
|
summaries_to_insert: List[SummaryObject] = []
|
|
|
|
def process_toc(items: List[Dict[str, Any]], parent_path: str = "") -> None:
|
|
"""Parcourt récursivement la TOC pour créer des Summary."""
|
|
for item in items:
|
|
title: str = item.get("title", "")
|
|
level: int = item.get("level", 1)
|
|
path: str = f"{parent_path} > {title}" if parent_path else title
|
|
|
|
summary_obj: SummaryObject = {
|
|
"sectionPath": path,
|
|
"title": title,
|
|
"level": level,
|
|
|
|
# ⚠️ PROBLÈME ICI : Si summaries_content est vide,
|
|
# on utilise juste le titre comme texte !
|
|
"text": summaries_content.get(title, title),
|
|
|
|
"concepts": item.get("concepts", []),
|
|
|
|
# ⚠️ PROBLÈME : Toujours 0, jamais calculé !
|
|
"chunksCount": 0,
|
|
|
|
"document": {
|
|
"sourceId": doc_name,
|
|
},
|
|
}
|
|
summaries_to_insert.append(summary_obj)
|
|
|
|
# Traiter les sous-sections récursivement
|
|
if "children" in item:
|
|
process_toc(item["children"], path)
|
|
|
|
process_toc(toc)
|
|
|
|
# Insertion batch dans Weaviate
|
|
summary_collection.data.insert_many(summaries_to_insert)
|
|
return len(summaries_to_insert)
|
|
```
|
|
|
|
### 3.2 Appel dans le Pipeline
|
|
|
|
**Fichier**: `utils/weaviate_ingest.py:844-845`
|
|
|
|
```python
|
|
# Dans la fonction ingest_document()
|
|
if ingest_summary_collection and toc:
|
|
ingest_summaries(client, doc_name, toc, {}) # ← {} = VIDE !
|
|
```
|
|
|
|
**PROBLÈME** : Le dictionnaire `summaries_content` passé est **VIDE** (`{}`).
|
|
|
|
**Résultat** : Ligne 686 → `summaries_content.get(title, title)` retourne juste `title` !
|
|
|
|
**Exemple**:
|
|
```python
|
|
title = "Peirce: CP 5.314"
|
|
summaries_content = {} # VIDE
|
|
|
|
text = summaries_content.get(title, title)
|
|
# → text = "Peirce: CP 5.314" (car title pas dans dict vide)
|
|
|
|
# Attendu:
|
|
# text = "Ce passage explore la théorie de la sémiose comme processus triadique..."
|
|
```
|
|
|
|
### 3.3 Source de la TOC
|
|
|
|
La TOC vient de l'extraction LLM :
|
|
|
|
**Fichier**: `utils/llm_toc.py` (étape 5 du pipeline)
|
|
|
|
```python
|
|
def extract_toc_from_markdown(markdown_text: str, ...) -> List[TOCEntry]:
|
|
"""Extrait la TOC via LLM (Ollama ou Mistral).
|
|
|
|
Résultat:
|
|
[
|
|
{
|
|
"title": "Peirce: CP 5.314",
|
|
"level": 1,
|
|
"page": null,
|
|
"children": [
|
|
{
|
|
"title": "La sémiose et les catégories",
|
|
"level": 2,
|
|
"page": null
|
|
}
|
|
]
|
|
},
|
|
...
|
|
]
|
|
"""
|
|
```
|
|
|
|
**Note**: La TOC contient **seulement les titres**, pas les résumés.
|
|
|
|
---
|
|
|
|
## 4. Pourquoi les Summary sont Vides
|
|
|
|
### 4.1 Problème #1 : Pas de Génération de Résumés LLM
|
|
|
|
**Constat**: Le pipeline PDF ne génère **jamais** de résumés pour les sections.
|
|
|
|
**Étapes du pipeline actuel** (`utils/pdf_pipeline.py`):
|
|
```
|
|
[1] OCR → Texte brut
|
|
[2] Markdown → Markdown structuré
|
|
[3] Images → Extraction images
|
|
[4] Metadata → Titre, auteur, année
|
|
[5] TOC → Table des matières (TITRES SEULEMENT)
|
|
[6] Classify → Classification sections
|
|
[7] Chunking → Découpage en chunks
|
|
[8] Cleaning → Nettoyage chunks
|
|
[9] Validation → Validation + concepts
|
|
[10] Ingestion → Insertion Weaviate
|
|
```
|
|
|
|
**Manque** : Étape de génération de résumés par section !
|
|
|
|
**Ce qui devrait exister** :
|
|
```
|
|
[5.5] Summarization → Générer résumé LLM pour chaque section TOC
|
|
Input: Section text (tous les chunks d'une section)
|
|
Output: {"Peirce: CP 5.314": "Ce passage explore..."}
|
|
```
|
|
|
|
### 4.2 Problème #2 : chunksCount Toujours à 0
|
|
|
|
**Constat**: Le champ `chunksCount` est hardcodé à 0.
|
|
|
|
**Fichier**: `utils/weaviate_ingest.py:688`
|
|
|
|
```python
|
|
"chunksCount": 0, # ← Hardcodé, jamais calculé !
|
|
```
|
|
|
|
**Ce qui devrait être fait** :
|
|
|
|
```python
|
|
def calculate_chunks_count(chunks: List[Dict], section_path: str) -> int:
|
|
"""Compte combien de chunks appartiennent à cette section."""
|
|
count = 0
|
|
for chunk in chunks:
|
|
if chunk.get("sectionPath", "").startswith(section_path):
|
|
count += 1
|
|
return count
|
|
|
|
# Dans process_toc():
|
|
chunks_count = calculate_chunks_count(all_chunks, path)
|
|
|
|
summary_obj: SummaryObject = {
|
|
...
|
|
"chunksCount": chunks_count, # ← Calculé dynamiquement
|
|
...
|
|
}
|
|
```
|
|
|
|
**Pourquoi ce n'est pas fait** :
|
|
- La fonction `ingest_summaries()` n'a pas accès à la liste des chunks
|
|
- Les chunks sont insérés APRÈS les summaries dans le pipeline
|
|
- Ordre incorrect : devrait être Chunks → Summaries (pour compter)
|
|
|
|
### 4.3 Problème #3 : Concepts Vides
|
|
|
|
**Constat**: Le champ `concepts` est toujours vide.
|
|
|
|
**Fichier**: `utils/weaviate_ingest.py:687`
|
|
|
|
```python
|
|
"concepts": item.get("concepts", []), # ← TOC n'a jamais de concepts
|
|
```
|
|
|
|
**Explication**: La TOC extraite par LLM ne contient que `{title, level, page}`, pas de concepts.
|
|
|
|
**Ce qui devrait être fait** :
|
|
|
|
Les concepts devraient être extraits lors de la génération du résumé :
|
|
|
|
```python
|
|
# Étape 5.5 - Summarization (à créer)
|
|
def generate_section_summary(section_text: str) -> Dict[str, Any]:
|
|
"""Génère résumé + concepts via LLM."""
|
|
|
|
prompt = f"""Résume cette section et extrais les concepts clés.
|
|
|
|
Section:
|
|
{section_text}
|
|
|
|
Réponds en JSON:
|
|
{{
|
|
"summary": "Résumé en 100-200 mots...",
|
|
"concepts": ["concept1", "concept2", ...]
|
|
}}
|
|
"""
|
|
|
|
response = llm.generate(prompt)
|
|
return json.loads(response)
|
|
|
|
# Résultat:
|
|
{
|
|
"summary": "Ce passage explore la théorie de la sémiose...",
|
|
"concepts": ["sémiose", "triade", "signe", "interprétant", "représentamen"]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Comment Corriger le Problème
|
|
|
|
### 5.1 Solution Complète : Ajouter Étape de Summarization
|
|
|
|
**Créer nouveau module** : `utils/llm_summarizer.py`
|
|
|
|
```python
|
|
"""LLM-based section summarization for Library RAG.
|
|
|
|
Generates summaries and extracts concepts for each section in the TOC.
|
|
"""
|
|
|
|
from typing import Dict, List, Any
|
|
from utils.llm_structurer import get_llm_client
|
|
import json
|
|
|
|
def generate_summaries_for_toc(
|
|
toc: List[Dict[str, Any]],
|
|
chunks: List[Dict[str, Any]],
|
|
llm_provider: str = "ollama"
|
|
) -> Dict[str, Dict[str, Any]]:
|
|
"""Generate LLM summaries for each section in the TOC.
|
|
|
|
Args:
|
|
toc: Table of contents with hierarchical structure.
|
|
chunks: All document chunks with sectionPath.
|
|
llm_provider: "ollama" or "mistral".
|
|
|
|
Returns:
|
|
Dict mapping section title to {summary, concepts}.
|
|
|
|
Example:
|
|
>>> summaries = generate_summaries_for_toc(toc, chunks)
|
|
>>> summaries["Peirce: CP 5.314"]
|
|
{
|
|
"summary": "Ce passage explore la sémiose...",
|
|
"concepts": ["sémiose", "triade", "signe"]
|
|
}
|
|
"""
|
|
|
|
llm = get_llm_client(llm_provider)
|
|
summaries_content: Dict[str, Dict[str, Any]] = {}
|
|
|
|
def process_section(item: Dict[str, Any], parent_path: str = "") -> None:
|
|
title = item.get("title", "")
|
|
path = f"{parent_path} > {title}" if parent_path else title
|
|
|
|
# Collecter tous les chunks de cette section
|
|
section_chunks = [
|
|
chunk for chunk in chunks
|
|
if chunk.get("sectionPath", "").startswith(path)
|
|
]
|
|
|
|
if not section_chunks:
|
|
# Pas de chunks, utiliser juste le titre
|
|
summaries_content[title] = {
|
|
"summary": title,
|
|
"concepts": []
|
|
}
|
|
else:
|
|
# Générer résumé via LLM
|
|
section_text = "\n\n".join([c.get("text", "") for c in section_chunks[:10]]) # Max 10 chunks
|
|
|
|
prompt = f"""Résume cette section philosophique en 100-200 mots et extrais les 5-10 concepts clés.
|
|
|
|
Section: {title}
|
|
|
|
Texte:
|
|
{section_text}
|
|
|
|
Réponds en JSON:
|
|
{{
|
|
"summary": "Résumé de la section...",
|
|
"concepts": ["concept1", "concept2", ...]
|
|
}}
|
|
"""
|
|
|
|
try:
|
|
response = llm.generate(prompt, max_tokens=500)
|
|
result = json.loads(response)
|
|
summaries_content[title] = result
|
|
except Exception as e:
|
|
print(f"Erreur génération résumé pour {title}: {e}")
|
|
summaries_content[title] = {
|
|
"summary": title,
|
|
"concepts": []
|
|
}
|
|
|
|
# Traiter sous-sections récursivement
|
|
if "children" in item:
|
|
for child in item["children"]:
|
|
process_section(child, path)
|
|
|
|
for item in toc:
|
|
process_section(item)
|
|
|
|
return summaries_content
|
|
```
|
|
|
|
**Modifier le pipeline** : `utils/weaviate_ingest.py`
|
|
|
|
```python
|
|
def ingest_document(
|
|
doc_name: str,
|
|
chunks: List[Dict[str, Any]],
|
|
metadata: Dict[str, Any],
|
|
...,
|
|
ingest_summary_collection: bool = False,
|
|
) -> IngestResult:
|
|
|
|
# ... (code existant pour chunks)
|
|
|
|
# NOUVEAU : Générer résumés APRÈS avoir les chunks
|
|
if ingest_summary_collection and toc:
|
|
from utils.llm_summarizer import generate_summaries_for_toc
|
|
|
|
# Générer résumés LLM pour chaque section
|
|
summaries_content = generate_summaries_for_toc(toc, chunks, llm_provider="ollama")
|
|
|
|
# Transformer en format pour ingest_summaries
|
|
summaries_text = {
|
|
title: content["summary"]
|
|
for title, content in summaries_content.items()
|
|
}
|
|
|
|
# Ajouter concepts dans la TOC
|
|
def enrich_toc_with_concepts(items: List[Dict]) -> None:
|
|
for item in items:
|
|
title = item.get("title", "")
|
|
if title in summaries_content:
|
|
item["concepts"] = summaries_content[title]["concepts"]
|
|
if "children" in item:
|
|
enrich_toc_with_concepts(item["children"])
|
|
|
|
enrich_toc_with_concepts(toc)
|
|
|
|
# Insérer avec vrais résumés
|
|
ingest_summaries(client, doc_name, toc, summaries_text)
|
|
```
|
|
|
|
### 5.2 Solution Rapide : Calculer chunksCount Dynamiquement
|
|
|
|
**Modifier** : `utils/weaviate_ingest.py:ingest_summaries()`
|
|
|
|
```python
|
|
def ingest_summaries(
|
|
client: WeaviateClient,
|
|
doc_name: str,
|
|
toc: List[Dict[str, Any]],
|
|
summaries_content: Dict[str, str],
|
|
chunks: List[Dict[str, Any]] = [], # ← NOUVEAU paramètre
|
|
) -> int:
|
|
|
|
summaries_to_insert: List[SummaryObject] = []
|
|
|
|
def count_chunks_for_section(section_path: str) -> int:
|
|
"""Compte chunks appartenant à cette section."""
|
|
count = 0
|
|
for chunk in chunks:
|
|
if chunk.get("sectionPath", "").startswith(section_path):
|
|
count += 1
|
|
return count
|
|
|
|
def process_toc(items: List[Dict[str, Any]], parent_path: str = "") -> None:
|
|
for item in items:
|
|
title: str = item.get("title", "")
|
|
level: int = item.get("level", 1)
|
|
path: str = f"{parent_path} > {title}" if parent_path else title
|
|
|
|
summary_obj: SummaryObject = {
|
|
"sectionPath": path,
|
|
"title": title,
|
|
"level": level,
|
|
"text": summaries_content.get(title, title),
|
|
"concepts": item.get("concepts", []),
|
|
|
|
# ✅ CORRECTIF : Calculer dynamiquement
|
|
"chunksCount": count_chunks_for_section(path),
|
|
|
|
"document": {
|
|
"sourceId": doc_name,
|
|
},
|
|
}
|
|
summaries_to_insert.append(summary_obj)
|
|
|
|
if "children" in item:
|
|
process_toc(item["children"], path)
|
|
|
|
process_toc(toc)
|
|
|
|
# ... (reste du code)
|
|
```
|
|
|
|
**Modifier appel** : `utils/weaviate_ingest.py:844-845`
|
|
|
|
```python
|
|
if ingest_summary_collection and toc:
|
|
# ✅ Passer les chunks pour calcul de chunksCount
|
|
ingest_summaries(client, doc_name, toc, {}, chunks)
|
|
```
|
|
|
|
### 5.3 Solution Minimale : Ré-injecter avec Vraies Données
|
|
|
|
Si vous avez déjà les résumés dans les JSON :
|
|
|
|
```python
|
|
# Script de correction rapide
|
|
import json
|
|
import weaviate
|
|
from pathlib import Path
|
|
|
|
# Charger le JSON avec les résumés
|
|
chunks_file = Path("output/peirce_collected_papers_fixed/peirce_collected_papers_fixed_chunks.json")
|
|
with open(chunks_file, 'r', encoding='utf-8') as f:
|
|
data = json.load(f)
|
|
|
|
# Vérifier s'il y a des résumés
|
|
if 'summaries' in data:
|
|
print(f"Trouvé {len(data['summaries'])} résumés dans le JSON")
|
|
|
|
# Connecter à Weaviate
|
|
client = weaviate.connect_to_local()
|
|
|
|
# Supprimer anciens Summary
|
|
summaries = client.collections.get("Summary")
|
|
summaries.data.delete_many(
|
|
where=Filter.by_property("document").by_property("sourceId").equal("peirce_collected_papers_fixed")
|
|
)
|
|
|
|
# Réinsérer avec vrais résumés
|
|
from utils.weaviate_ingest import ingest_summaries
|
|
|
|
toc = data['metadata']['toc']
|
|
chunks = data['chunks']
|
|
|
|
# Extraire résumés du JSON
|
|
summaries_content = {
|
|
s['title']: s['text']
|
|
for s in data['summaries']
|
|
}
|
|
|
|
# Réinjecter
|
|
count = ingest_summaries(client, "peirce_collected_papers_fixed", toc, summaries_content, chunks)
|
|
print(f"Réinséré {count} résumés")
|
|
|
|
client.close()
|
|
else:
|
|
print("❌ Pas de résumés dans le JSON - il faut les générer avec LLM")
|
|
```
|
|
|
|
---
|
|
|
|
## 6. Résumé Visual
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ PIPELINE ACTUEL (CASSÉ) │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
|
|
PDF → OCR → Markdown → TOC Extraction (LLM)
|
|
│
|
|
└─► toc = [
|
|
{"title": "Peirce: CP 5.314", "level": 1},
|
|
{"title": "La sémiose", "level": 2}
|
|
]
|
|
|
|
↓
|
|
|
|
Chunking (LLM) → chunks = [
|
|
{"text": "Un signe...", "sectionPath": "Peirce: CP 5.314 > La sémiose"},
|
|
{"text": "La sémiose...", "sectionPath": "Peirce: CP 5.314 > La sémiose"},
|
|
...
|
|
]
|
|
|
|
↓
|
|
|
|
Ingestion → ingest_summaries(client, doc_name, toc, {}) ← VIDE !
|
|
│
|
|
└─► Summary créés avec:
|
|
- text: "Peirce: CP 5.314" (juste le titre)
|
|
- concepts: []
|
|
- chunksCount: 0
|
|
|
|
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ PIPELINE CORRIGÉ (ATTENDU) │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
|
|
PDF → OCR → Markdown → TOC Extraction → Chunking
|
|
│
|
|
↓
|
|
Summarization (LLM) ← NOUVEAU !
|
|
│
|
|
└─► summaries_content = {
|
|
"Peirce: CP 5.314": {
|
|
"summary": "Ce passage explore...",
|
|
"concepts": ["sémiose", "triade"]
|
|
}
|
|
}
|
|
|
|
↓
|
|
|
|
Ingestion → ingest_summaries(client, doc_name, toc, summaries_content, chunks)
|
|
│
|
|
└─► Summary créés avec:
|
|
- text: "Ce passage explore la théorie de la sémiose..." ✅
|
|
- concepts: ["sémiose", "triade", "signe"] ✅
|
|
- chunksCount: 23 ✅
|
|
```
|
|
|
|
---
|
|
|
|
## 7. Conclusion
|
|
|
|
### État Actuel
|
|
|
|
**Summary → Chunk** : ❌ LIEN CASSÉ
|
|
|
|
| Aspect | Actuel | Attendu | Status |
|
|
|--------|--------|---------|--------|
|
|
| **text** | "Peirce: CP 5.314" | "Ce passage explore..." | ❌ Vide |
|
|
| **concepts** | `[]` | `["sémiose", "triade"]` | ❌ Vide |
|
|
| **chunksCount** | 0 | 23 | ❌ Faux |
|
|
| **sectionPath** | ✅ Correct | ✅ Correct | ✅ OK |
|
|
|
|
### Lien Théorique vs Réel
|
|
|
|
**Théorique** (design prévu):
|
|
```
|
|
Summary.sectionPath = "Peirce: CP 5.314 > La sémiose"
|
|
↓ LIEN
|
|
Chunk.sectionPath = "Peirce: CP 5.314 > La sémiose"
|
|
Chunk.sectionPath = "Peirce: CP 5.314 > La sémiose"
|
|
... (23 chunks)
|
|
```
|
|
|
|
**Réel** (implémentation actuelle):
|
|
```
|
|
Summary.sectionPath = "Peirce: CP 5.314" ✅ OK
|
|
Summary.chunksCount = 0 ❌ FAUX
|
|
Summary.text = "Peirce: CP 5.314" ❌ VIDE
|
|
|
|
Chunk.sectionPath = "Peirce: CP 5.314" ✅ OK
|
|
Chunk.text = "Un signe, ou representamen..." ✅ OK
|
|
```
|
|
|
|
**LIEN** : ⚠️ Existe techniquement (sectionPath identique) mais inutilisable car Summary vides.
|
|
|
|
### Actions Requises
|
|
|
|
**Priorité 1** : Générer résumés LLM (créer `llm_summarizer.py`)
|
|
**Priorité 2** : Calculer `chunksCount` dynamiquement
|
|
**Priorité 3** : Extraire concepts pour Summary
|
|
|
|
**ROI** : Activer recherche hiérarchique Summary → Chunk (+30% précision)
|
|
|
|
---
|
|
|
|
**Dernière mise à jour**: 2026-01-03
|
|
**Auteur**: Analyse du code source
|
|
**Version**: 1.0
|