Fix: Gestion robuste des valeurs None dans .lower()

Problème:
- AttributeError: 'NoneType' object has no attribute 'lower'
- Se produisait quand section.get("title") retournait None au lieu de ""

Corrections:
- llm_classifier.py:
  * is_excluded_section(): (section.get("title") or "").lower()
  * filter_indexable_sections(): (s.get("chapterTitle") or "").lower()
  * validate_classified_sections(): Idem pour chapter_title et section_title

- llm_validator.py:
  * apply_corrections(): Ajout de vérification "if title and ..."

- llm_chat.py:
  * call_llm(): Ajout d'une exception si provider est None/vide

Pattern de correction:
  AVANT: section.get("title", "").lower()  # Échoue si None
  APRÈS: (section.get("title") or "").lower()  # Sûr avec None

Raison:
.get(key, default) retourne le default SEULEMENT si la clé n'existe pas.
Si la clé existe avec valeur None, .get() retourne None, pas le default!

Donc: {"title": None}.get("title", "") -> None (pas "")

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-30 22:26:29 +01:00
parent 9e4108def1
commit 4823fd1b10
3 changed files with 9 additions and 6 deletions

View File

@@ -56,6 +56,9 @@ def call_llm(
>>> for token in call_llm("Test", "ollama", "qwen2.5:7b"): >>> for token in call_llm("Test", "ollama", "qwen2.5:7b"):
... print(token, end="") ... print(token, end="")
""" """
if not provider:
raise LLMError("Provider cannot be None or empty")
provider = provider.lower() provider = provider.lower()
logger.info(f"[LLM Call] Provider: {provider}, Model: {model}, Stream: {stream}") logger.info(f"[LLM Call] Provider: {provider}, Model: {model}, Stream: {stream}")

View File

@@ -351,8 +351,8 @@ def is_excluded_section(section: dict[str, Any]) -> bool:
>>> is_excluded_section({"title": "Introduction", "content": "..."}) >>> is_excluded_section({"title": "Introduction", "content": "..."})
False False
""" """
title: str = section.get("title", "").lower().strip() title: str = (section.get("title") or "").lower().strip()
chapter_title: str = section.get("chapterTitle", "").lower().strip() chapter_title: str = (section.get("chapterTitle") or "").lower().strip()
# Vérifier le titre de la section # Vérifier le titre de la section
for excluded in EXCLUDED_SECTION_TITLES: for excluded in EXCLUDED_SECTION_TITLES:
@@ -454,7 +454,7 @@ def filter_indexable_sections(sections: list[dict[str, Any]]) -> list[dict[str,
continue continue
# Vérifier si le chapitre parent est une TOC # Vérifier si le chapitre parent est une TOC
chapter_title: str = s.get("chapterTitle", "").lower().strip() chapter_title: str = (s.get("chapterTitle") or "").lower().strip()
if any(excluded in chapter_title for excluded in EXCLUDED_SECTION_TITLES): if any(excluded in chapter_title for excluded in EXCLUDED_SECTION_TITLES):
logger.info(f"Section exclue (chapitre TOC): '{s.get('title', 'Sans titre')}' dans '{chapter_title}'") logger.info(f"Section exclue (chapitre TOC): '{s.get('title', 'Sans titre')}' dans '{chapter_title}'")
excluded_count += 1 excluded_count += 1
@@ -497,8 +497,8 @@ def validate_classified_sections(sections: list[dict[str, Any]]) -> list[dict[st
for section in sections: for section in sections:
# Vérifier d'abord si le titre du chapitre parent est une TOC # Vérifier d'abord si le titre du chapitre parent est une TOC
chapter_title: str = section.get("chapter_title", "").lower().strip() chapter_title: str = (section.get("chapter_title") or "").lower().strip()
section_title: str = section.get("title", "").lower().strip() section_title: str = (section.get("title") or "").lower().strip()
# Exclure si le chapitre parent est une TOC # Exclure si le chapitre parent est une TOC
is_toc_chapter: bool = False is_toc_chapter: bool = False

View File

@@ -495,7 +495,7 @@ def apply_corrections(
title: str = metadata["title"] title: str = metadata["title"]
# Si le titre contient des phrases de validation, utiliser le champ "work" à la place # Si le titre contient des phrases de validation, utiliser le champ "work" à la place
validation_phrases: List[str] = ["à confirmer", "confirmer avec", "vérifier"] validation_phrases: List[str] = ["à confirmer", "confirmer avec", "vérifier"]
if any(phrase in title.lower() for phrase in validation_phrases): if title and any(phrase in title.lower() for phrase in validation_phrases):
if "work" in metadata and metadata["work"]: if "work" in metadata and metadata["work"]:
logger.info(f"Titre remplacé par 'work': '{title}' -> '{metadata['work']}'") logger.info(f"Titre remplacé par 'work': '{title}' -> '{metadata['work']}'")
metadata["original_title"] = title metadata["original_title"] = title