fix: Prevent context manager conflict by never calling simple_search from hierarchical_search

- Add @contextmanager decorator for proper exception handling
- Remove all simple_search() calls from within hierarchical_search()
- Return mode='error' to signal fallback needed
- Handle fallback in search_passages() (outside context manager)
- This eliminates 'generator didn't stop after throw()' error
This commit is contained in:
2026-01-01 15:27:14 +01:00
parent 22ac9a030e
commit 9c6ba3f4a1

View File

@@ -132,7 +132,10 @@ def get_weaviate_client() -> Generator[Optional[weaviate.WeaviateClient], None,
yield None yield None
finally: finally:
if client: if client:
try:
client.close() client.close()
except Exception as e:
print(f"Erreur fermeture client Weaviate: {e}")
def get_collection_stats() -> Optional[CollectionStats]: def get_collection_stats() -> Optional[CollectionStats]:
@@ -314,21 +317,18 @@ def hierarchical_search(
- total_chunks: Total number of chunks found - total_chunks: Total number of chunks found
- fallback_reason: Explanation if forced but 0 results (optional) - fallback_reason: Explanation if forced but 0 results (optional)
""" """
try:
with get_weaviate_client() as client: with get_weaviate_client() as client:
if client is None: if client is None:
# Return early if forced, otherwise signal fallback # Return empty result - let caller decide fallback
if force_hierarchical:
return { return {
"mode": "hierarchical", "mode": "hierarchical" if force_hierarchical else "error",
"sections": [], "sections": [],
"results": [], "results": [],
"total_chunks": 0, "total_chunks": 0,
"fallback_reason": "Weaviate client unavailable", "fallback_reason": "Weaviate client unavailable",
} }
# Set flag to fallback outside context manager
raise ValueError("FALLBACK_TO_SIMPLE")
try:
# ═══════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════
# STAGE 1: Search Summary collection for relevant sections # STAGE 1: Search Summary collection for relevant sections
# ═══════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════
@@ -345,18 +345,14 @@ def hierarchical_search(
) )
if not summaries_result.objects: if not summaries_result.objects:
# No summaries found # No summaries found - return empty result
if force_hierarchical:
# Forced hierarchical: return empty hierarchical result
return { return {
"mode": "hierarchical", "mode": "hierarchical" if force_hierarchical else "error",
"sections": [], "sections": [],
"results": [], "results": [],
"total_chunks": 0, "total_chunks": 0,
"fallback_reason": f"Aucune section pertinente trouvée (0/{sections_limit} summaries)", "fallback_reason": f"Aucune section pertinente trouvée (0/{sections_limit} summaries)",
} }
# Signal fallback outside context manager
raise ValueError("FALLBACK_TO_SIMPLE")
# Extract section data # Extract section data
sections_data = [] sections_data = []
@@ -405,21 +401,17 @@ def hierarchical_search(
sections_data = filtered_sections sections_data = filtered_sections
if not sections_data: if not sections_data:
# No sections match filters # No sections match filters - return empty result
if force_hierarchical:
# Forced hierarchical: return empty hierarchical result
filters_str = f"author={author_filter}" if author_filter else "" filters_str = f"author={author_filter}" if author_filter else ""
if work_filter: if work_filter:
filters_str += f", work={work_filter}" if filters_str else f"work={work_filter}" filters_str += f", work={work_filter}" if filters_str else f"work={work_filter}"
return { return {
"mode": "hierarchical", "mode": "hierarchical" if force_hierarchical else "error",
"sections": [], "sections": [],
"results": [], "results": [],
"total_chunks": 0, "total_chunks": 0,
"fallback_reason": f"Aucune section ne correspond aux filtres ({filters_str})", "fallback_reason": f"Aucune section ne correspond aux filtres ({filters_str})",
} }
# Signal fallback outside context manager
raise ValueError("FALLBACK_TO_SIMPLE")
# ═══════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════
# STAGE 2: Search Chunk collection filtered by sections # STAGE 2: Search Chunk collection filtered by sections
@@ -479,27 +471,15 @@ def hierarchical_search(
"total_chunks": len(all_chunks), "total_chunks": len(all_chunks),
} }
except ValueError as e:
# Check if this is our fallback signal
if str(e) == "FALLBACK_TO_SIMPLE":
# Fallback to simple search (outside context manager)
results = simple_search(query, limit, author_filter, work_filter)
return {
"mode": "simple",
"results": results,
"total_chunks": len(results),
}
# Re-raise if not our signal
raise
except Exception as e: except Exception as e:
# Handle errors within the try block (inside 'with')
print(f"Erreur recherche hiérarchique: {e}") print(f"Erreur recherche hiérarchique: {e}")
import traceback import traceback
traceback.print_exc() traceback.print_exc()
# CRITICAL: Never call simple_search() here - we're still cleaning up the context manager! # Return empty result (don't call simple_search here!)
# Instead, return empty result and let the caller decide if they want to retry
return { return {
"mode": "hierarchical" if force_hierarchical else "simple", "mode": "hierarchical" if force_hierarchical else "error",
"sections": [], "sections": [],
"results": [], "results": [],
"total_chunks": 0, "total_chunks": 0,
@@ -615,7 +595,7 @@ def search_passages(
# Execute appropriate search strategy # Execute appropriate search strategy
if use_hierarchical: if use_hierarchical:
return hierarchical_search( result = hierarchical_search(
query=query, query=query,
limit=limit, limit=limit,
author_filter=author_filter, author_filter=author_filter,
@@ -623,6 +603,17 @@ def search_passages(
sections_limit=sections_limit, sections_limit=sections_limit,
force_hierarchical=(force_mode == "hierarchical"), # No fallback if explicitly forced force_hierarchical=(force_mode == "hierarchical"), # No fallback if explicitly forced
) )
# If hierarchical search failed and wasn't forced, fallback to simple search
if result.get("mode") == "error" and force_mode != "hierarchical":
results = simple_search(query, limit, author_filter, work_filter)
return {
"mode": "simple",
"results": results,
"total_chunks": len(results),
}
return result
else: else:
results = simple_search(query, limit, author_filter, work_filter) results = simple_search(query, limit, author_filter, work_filter)
return { return {