from langchain.schema import HumanMessage import json import streamlit as st from config import neo4j_driver, sparse_index as indexB, llm from pinecone_utilsB import hybrid_search from neo4j.exceptions import CypherSyntaxError import re import logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) def extract_cypher_query(llm_output): """ Extrait la requête Cypher valide de la sortie du LLM. """ # Utiliser une expression régulière pour trouver la requête Cypher cypher_pattern = r"(MATCH|CREATE|MERGE|RETURN|WITH|DELETE|SET|REMOVE|UNWIND|CALL)[\s\S]*?;" match = re.search(cypher_pattern, llm_output) if match: return match.group(0) # Retourne la requête Cypher trouvée else: # Si aucune requête n'est trouvée, essayer de retourner la dernière ligne lines = llm_output.split("\n") for line in reversed(lines): if line.strip().endswith(";"): return line.strip() raise ValueError("Aucune requête Cypher valide trouvée dans la sortie du LLM.") # Function to generate Cypher queries using an LLM def generate_cypher_query(user_query): """Génère une requête Cypher à l'aide du LLM et extrait la partie valide.""" # Liste des nœuds et relations autorisés allowed_relationships = [ "CONNAÎT", "SITUE_DANS", "FAIT_PARTIE_DE", "SE_PRODUIT_PENDANT", "IMPLIQUE", "S'OPPOSE_À", "CRÉÉ_PAR", "INSPIRÉ_PAR", "REPRÉSENTE", "TRANSFORME", "A_PARTICIPÉ_À", "A_CAUSÉ", "POSSEDE", "A_MODIFIÉ", "A_SURVÉCU_À", "A_ÉTÉ_CRÉÉ_PENDANT", "A_ÉTÉ_TRANSMIS_PENDANT", "EST_ISSU_DE", "APPARTIENT_À" ] allowed_nodes = [ "Personnage", "Organisation", "Lieu","Pays", "Groupe", "Événement", "Objet", "Théme", ] # Construire le prompt avec des instructions claires prompt = f""" Vous êtes un générateur de requêtes Cypher pour une base de données Neo4j. Étant donné une demande de l'utilisateur, générez une requête Cypher correspondante. **Instructions supplémentaires** : - Utilisez `DISTINCT` pour éviter les répétitions. - Optimisez la requête pour éviter les chemins redondants. - Tous les nœuds ont uniquement la propriété `id`. Utilisez `id` pour filtrer ou retourner des valeurs. - Utilisez uniquement les nœuds et relations autorisés suivants : **Nœuds autorisés** : {", ".join(allowed_nodes)} **Relations autorisées** : {", ".join(allowed_relationships)} **Exemples de requêtes** : - Demande : "Trouver tous les lieux mentionnés dans une histoire." Requête : MATCH (n:Personnage) RETURN n.id; - Demande : "Lister tous les personnages qui connaissent Zéphyrine." Requête : MATCH (p1:Personnage)-[:CONNAÎT]->(p2:Personnage {{name: "Zéphyrine"}}) RETURN DISTINCT p1.name **Format de la réponse** : - Ne fournissez **que la requête Cypher**, sans explications ni commentaires. - La requête doit commencer par `MATCH`, `CREATE`, `MERGE`, `RETURN`, etc. - La requête doit se terminer par un point-virgule (`;`). - Si vous ne pouvez pas générer de requête, retournez une chaîne vide. **Demande de l'utilisateur** : {user_query} **Requête Cypher** : """ # Générer la réponse avec le LLM response = llm.invoke(prompt) logger.debug(f"Sortie brute du LLM : {response.content}") # Extraire la requête Cypher valide try: cypher_query = extract_cypher_query(response.content) return cypher_query except ValueError as e: print(f"Erreur lors de l'extraction de la requête Cypher : {e}") return None # Function to execute Cypher queries in Neo4j def execute_cypher_query(cypher_query): """Exécute une requête Cypher dans Neo4j et retourne les résultats.""" if not cypher_query: return [] try: with neo4j_driver.session() as session: result = session.run(cypher_query) return [record for record in result] except CypherSyntaxError as e: print(f"Erreur de syntaxe Cypher : {e}") return [] # Function to merge hybrid search and Neo4j results def merge_results(hybrid_results, neo4j_results): """Merge results from hybrid search and Neo4j into a unified list.""" combined_results = [] # Add hybrid search results for doc in hybrid_results: combined_results.append({ "type": "text", "content": doc["text"], # Utilisez la clé "text" du document hybride "score": 1.0 }) # Add Neo4j search results for record in neo4j_results: combined_results.append({ "type": "entity", "content": str(record), # Convertir en chaîne de caractères si nécessaire "score": 0.80 # Score par défaut pour les résultats Neo4j }) # Sort combined results by score (or any other criteria) combined_results.sort(key=lambda x: x["score"], reverse=True) return combined_results # Unified search function def unified_search(query, k, alpha, similarity_threshold): """Perform a unified search combining hybrid search and Neo4j knowledge graph search.""" # Step 1: Perform hybrid search hybrid_results = hybrid_search(query, alpha=alpha, k=k, similarity_threshold=similarity_threshold) #print(f"résultat recherche hybride: {hybrid_results}") # Step 2: Generate and execute Cypher query cypher_query = generate_cypher_query(query) print(f"Generated Cypher query: {cypher_query}") neo4j_results = execute_cypher_query(cypher_query) logger.debug(f"Résultats Neo4j AVANT filtration : {neo4j_results}") # Step 3: Merge results final_results = merge_results(hybrid_results, neo4j_results) return final_results