paint-brush
Amélioration de RAG avec des graphiques de connaissances : intégration de Llama 3.1, NVIDIA NIM et LangChain pour l'IA dynamiquepar@neo4j
Nouvelle histoire

Amélioration de RAG avec des graphiques de connaissances : intégration de Llama 3.1, NVIDIA NIM et LangChain pour l'IA dynamique

par Neo4j9m2024/10/22
Read on Terminal Reader

Trop long; Pour lire

Cet article démontre l'utilisation de Llama 3.1, NVIDIA NIM et LangChain pour créer un agent basé sur un graphe de connaissances pour la génération augmentée de récupération (RAG), exploitant les données structurées et la génération de requêtes dynamiques pour améliorer la récupération des informations et la précision des réponses.
featured image - Amélioration de RAG avec des graphiques de connaissances : intégration de Llama 3.1, NVIDIA NIM et LangChain pour l'IA dynamique
Neo4j HackerNoon profile picture
0-item
1-item



Alors que la plupart des gens se concentrent sur la génération augmentée de données (RAG) plutôt que sur du texte non structuré, comme des documents d'entreprise ou de la documentation, je suis plutôt optimiste quant aux systèmes de récupération par rapport aux informations structurées, en particulier les graphes de connaissances . GraphRAG a suscité beaucoup d'enthousiasme, en particulier l'implémentation de Microsoft. Cependant, dans leur implémentation, les données d'entrée sont du texte non structuré sous forme de documents, qui est transformé en un graphe de connaissances à l'aide d'un modèle de langage étendu (LLM).


Dans cet article de blog, nous allons vous montrer comment implémenter un récupérateur sur un graphe de connaissances contenant des informations structurées du système de rapport des événements indésirables de la FDA (FAERS) , qui fournit des informations sur les événements indésirables des médicaments. Si vous avez déjà bricolé avec des graphes de connaissances et la récupération, votre première pensée pourrait être d'utiliser un LLM pour générer des requêtes de base de données afin de récupérer des informations pertinentes à partir d'un graphe de connaissances pour répondre à une question donnée. Cependant, la génération de requêtes de base de données à l'aide de LLM est encore en évolution et n'offre peut-être pas encore la solution la plus cohérente ou la plus robuste. Alors, quelles sont les alternatives viables à l'heure actuelle ?


À mon avis, la meilleure solution actuelle est la génération de requêtes dynamiques. Plutôt que de s'appuyer entièrement sur un LLM pour générer la requête complète, cette méthode utilise une couche logique qui génère de manière déterministe une requête de base de données à partir de paramètres d'entrée prédéfinis. Cette solution peut être mise en œuvre à l'aide d'un LLM avec prise en charge de l'appel de fonctions. L'avantage d'utiliser une fonction d'appel de fonctions réside dans la possibilité de définir à un LLM comment il doit préparer une entrée structurée pour une fonction. Cette approche garantit que le processus de génération de requêtes est contrôlé et cohérent tout en permettant une flexibilité de saisie utilisateur.


Flux de génération de requêtes dynamiques – Image de l'auteur


L'image illustre un processus de compréhension de la question d'un utilisateur pour récupérer des informations spécifiques. Le flux comprend trois étapes principales :


  1. Un utilisateur pose une question sur les effets secondaires courants du médicament Lyrica chez les personnes de moins de 35 ans.


  2. Le LLM décide quelle fonction appeler et quels paramètres sont nécessaires. Dans cet exemple, il a choisi une fonction nommée side_effects avec des paramètres incluant le médicament Lyrica et un âge maximum de 35 ans.


  3. La fonction et les paramètres identifiés sont utilisés pour générer de manière déterministe et dynamique une instruction de requête de base de données (Cypher) pour récupérer les informations pertinentes.


La prise en charge des appels de fonctions est essentielle pour les cas d'utilisation avancés de LLM, comme permettre aux LLM d'utiliser plusieurs récupérateurs en fonction de l'intention de l'utilisateur ou de créer des flux multi-agents. J'ai écrit quelques articles utilisant des LLM commerciaux avec prise en charge native des appels de fonctions. Cependant, nous utiliserons Llama-3.1, un LLM open source supérieur récemment publié avec prise en charge native des appels de fonctions.


Le code est disponible sur GitHub .

Configuration du Knowledge Graph

Nous utiliserons Neo4j, une base de données de graphes native, pour stocker les informations sur les événements indésirables. Vous pouvez configurer un projet Sandbox cloud gratuit fourni avec des FAERS pré-remplis en suivant ce lien .


L'instance de base de données instanciée possède un graphique avec le schéma suivant.


Schéma graphique des événements indésirables – Image de l'auteur


Le schéma est centré sur le nœud Cas, qui relie différents aspects d'un rapport sur la sécurité des médicaments, notamment les médicaments concernés, les réactions observées, les résultats et les thérapies prescrites. Chaque médicament est caractérisé par son caractère primaire, secondaire, concomitant ou interactif. Les cas sont également associés à des informations sur le fabricant, la tranche d'âge du patient et la source du rapport. Ce schéma permet de suivre et d'analyser les relations entre les médicaments, leurs réactions et leurs résultats de manière structurée.


Nous allons commencer par créer une connexion à la base de données en instanciant un objet Neo4jGraph :


 os.environ["NEO4J_URI"] = "bolt://18.206.157.187:7687" os.environ["NEO4J_USERNAME"] = "neo4j" os.environ["NEO4J_PASSWORD"] = "elevation-reservist-thousands" graph = Neo4jGraph(refresh_schema=False)


Configuration de l'environnement LLM

Il existe de nombreuses options pour héberger des LLM open source comme Llama-3.1. Nous utiliserons le catalogue d'API NVIDIA , qui fournit des microservices d'inférence NVIDIA NIM et prend en charge l'appel de fonctions pour les modèles Llama 3.1. Lorsque vous créez un compte, vous obtenez 1 000 jetons, ce qui est plus que suffisant pour suivre le processus. Vous devrez créer une clé API et la copier dans le bloc-notes :


 os.environ["NVIDIA_API_KEY"] = "nvapi-" llm = ChatNVIDIA(model="meta/llama-3.1-70b-instruct")


Nous utiliserons la version llama-3.1–70b car la version 8b présente quelques problèmes avec les paramètres facultatifs dans les définitions de fonctions.


L'avantage des microservices NVIDIA NIM est que vous pouvez facilement les héberger localement si vous avez des problèmes de sécurité ou autres, ils sont donc facilement échangeables et vous n'avez besoin d'ajouter qu'un paramètre d'URL à la configuration LLM :


 # connect to an local NIM running at localhost:8000, # specifying a specific model llm = ChatNVIDIA( base_url="http://localhost:8000/v1", model="meta/llama-3.1-70b-instruct" )

Définition de l'outil

Nous allons configurer un outil unique avec quatre paramètres optionnels. Nous allons construire une instruction Cypher correspondante basée sur ces paramètres pour récupérer les informations pertinentes du graphique de connaissances. Notre outil sera capable d'identifier les effets secondaires les plus fréquents en fonction du médicament saisi, de l'âge et du fabricant du médicament.


 @tool def get_side_effects( drug: Optional[str] = Field( description="disease mentioned in the question. Return None if no mentioned." ), min_age: Optional[int] = Field( description="Minimum age of the patient. Return None if no mentioned." ), max_age: Optional[int] = Field( description="Maximum age of the patient. Return None if no mentioned." ), manufacturer: Optional[str] = Field( description="manufacturer of the drug. Return None if no mentioned." ), ): """Useful for when you need to find common side effects.""" params = {} filters = [] side_effects_base_query = """ MATCH (c:Case)-[:HAS_REACTION]->(r:Reaction), (c)-[:IS_PRIMARY_SUSPECT]->(d:Drug) """ if drug and isinstance(drug, str): candidate_drugs = [el["candidate"] for el in get_candidates(drug, "drug")] if not candidate_drugs: return "The mentioned drug was not found" filters.append("d.name IN $drugs") params["drugs"] = candidate_drugs if min_age and isinstance(min_age, int): filters.append("c.age > $min_age ") params["min_age"] = min_age if max_age and isinstance(max_age, int): filters.append("c.age < $max_age ") params["max_age"] = max_age if manufacturer and isinstance(manufacturer, str): candidate_manufacturers = [ el["candidate"] for el in get_candidates(manufacturer, "manufacturer") ] if not candidate_manufacturers: return "The mentioned manufacturer was not found" filters.append( "EXISTS {(c)<-[:REGISTERED]-(:Manufacturer {manufacturerName: $manufacturer})}" ) params["manufacturer"] = candidate_manufacturers[0] if filters: side_effects_base_query += " WHERE " side_effects_base_query += " AND ".join(filters) side_effects_base_query += """ RETURN d.name AS drug, r.description AS side_effect, count(*) AS count ORDER BY count DESC LIMIT 10 """ print(f"Using parameters: {params}") data = graph.query(side_effects_base_query, params=params) return data


La fonction get_side_effects est conçue pour récupérer les effets secondaires courants des médicaments à partir d'un graphique de connaissances à l'aide de critères de recherche spécifiés. Elle accepte des paramètres facultatifs pour le nom du médicament, la tranche d'âge du patient et le fabricant du médicament afin de personnaliser la recherche. Chaque paramètre possède une description transmise à un LLM avec la description de la fonction, ce qui permet au LLM de comprendre comment les utiliser. La fonction construit ensuite une requête Cypher dynamique basée sur les entrées fournies, exécute cette requête sur le graphique de connaissances et renvoie les données d'effets secondaires résultantes.


Testons la fonction :


 get_side_effects("lyrica") # Using parameters: {'drugs': ['LYRICA', 'LYRICA CR']} # [{'drug': 'LYRICA', 'side_effect': 'Pain', 'count': 32}, # {'drug': 'LYRICA', 'side_effect': 'Fall', 'count': 21}, # {'drug': 'LYRICA', 'side_effect': 'Intentional product use issue', 'count': 20}, # {'drug': 'LYRICA', 'side_effect': 'Insomnia', 'count': 19}, # ...


Notre outil a d’abord mappé le médicament Lyrica mentionné dans la question aux valeurs « ['LYRICA', 'LYRICA CR'] » dans le graphique de connaissances, puis a exécuté une instruction Cypher correspondante pour trouver les effets secondaires les plus fréquents.

Agent LLM basé sur des graphiques

Il ne reste plus qu'à configurer un agent LLM qui pourra utiliser l'outil défini pour répondre aux questions sur les effets secondaires du médicament.


Flux de données de l'agent — Image de l'auteur


L'image montre un utilisateur interagissant avec un agent Llama 3.1 pour se renseigner sur les effets secondaires d'un médicament. L'agent accède à un outil d'effets secondaires qui récupère des informations à partir d'un graphique de connaissances pour fournir à l'utilisateur les données pertinentes.


Nous commencerons par définir le modèle d’invite :


 prompt = ChatPromptTemplate.from_messages( [ ( "system", "You are a helpful assistant that finds information about common side effects. " "If tools require follow up questions, " "make sure to ask the user for clarification. Make sure to include any " "available options that need to be clarified in the follow up questions " "Do only the things the user specifically requested. ", ), MessagesPlaceholder(variable_name="chat_history"), ("user", "{input}"), MessagesPlaceholder(variable_name="agent_scratchpad"), ] )


Le modèle d'invite inclut le message système, l'historique de discussion facultatif et la saisie utilisateur. L'agent_scratchpad est réservé au LLM, car il nécessite parfois plusieurs étapes pour répondre à la question, comme l'exécution et la récupération d'informations à partir d'outils.


La bibliothèque LangChain permet d'ajouter facilement des outils au LLM en utilisant la méthode bind_tools :


 tools = [get_side_effects] llm_with_tools = llm.bind_tools(tools=tools) agent = ( { "input": lambda x: x["input"], "chat_history": lambda x: _format_chat_history(x["chat_history"]) if x.get("chat_history") else [], "agent_scratchpad": lambda x: format_to_openai_function_messages( x["intermediate_steps"] ), } | prompt | llm_with_tools | OpenAIFunctionsAgentOutputParser() ) agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True).with_types( input_type=AgentInput, output_type=Output )


L'agent traite les entrées via des transformations et des gestionnaires qui formatent l'historique des conversations, appliquent le LLM avec les outils liés et analysent la sortie. Enfin, l'agent est configuré avec un exécuteur qui gère le flux d'exécution, spécifie les types d'entrée et de sortie et inclut des paramètres de verbosité pour une journalisation détaillée pendant l'exécution.


Testons l'agent :


 agent_executor.invoke( { "input": "What are the most common side effects when using lyrica for people below 35 years old?" } )


Résultats:


Exécution de l'agent — Image de l'auteur


Le LLM a identifié qu'il devait utiliser la fonction get_side_effects avec les arguments appropriés. La fonction génère ensuite dynamiquement une instruction Cypher, récupère les informations pertinentes et les renvoie au LLM pour générer la réponse finale.

Résumé

Les fonctions d'appel de fonctions constituent un ajout puissant aux modèles open source comme Llama 3.1, permettant des interactions plus structurées et contrôlées avec des sources de données et des outils externes. Au-delà de la simple interrogation de documents non structurés, les agents basés sur des graphes offrent des possibilités intéressantes d'interaction avec des graphes de connaissances et des données structurées. La facilité d'hébergement de ces modèles à l'aide de plateformes telles que les microservices NVIDIA NIM les rend de plus en plus accessibles.


Comme toujours, le code est disponible sur GitHub .


Pour en savoir plus sur ce sujet, rejoignez-nous à NODES 2024 le 7 novembre, notre conférence virtuelle gratuite pour développeurs sur les applications intelligentes, les graphes de connaissances et l'IA. Inscrivez-vous dès maintenant !