paint-brush
Comment créer une application d'assistant personnel alimentée par l'IApar@lablab
6,056 lectures
6,056 lectures

Comment créer une application d'assistant personnel alimentée par l'IA

par lablab.ai hackathons28m2023/07/04
Read on Terminal Reader

Trop long; Pour lire

Anthropic est une organisation de recherche axée sur le développement de systèmes d'IA avancés. Leur dernière création, Claude, est un assistant IA de nouvelle génération conçu pour être utile, honnête et inoffensif. LangChain est un outil permettant de créer des applications de modèle de langage de bout en bout. Il fournit un cadre robuste qui simplifie le processus de création, de gestion et de déploiement de modèles d'apprentissage linguistique.
featured image - Comment créer une application d'assistant personnel alimentée par l'IA
lablab.ai hackathons HackerNoon profile picture
0-item
1-item

Dans cet article, nous aimerions vous montrer comment combiner deux technologies ( LangChain et Anthropic ) et créer un assistant personnel basé sur la recherche.


De plus en plus de produits d'IA sont créés par les participants à nos hackathons. La prochaine opportunité pour les développeurs est notre hackathon conjoint avec Google Cloud Vertex AI , où chaque participant a la possibilité de créer sa propre application d'IA en utilisant les dernières technologies de Google Cloud . Nous allons parler ici d'une application de l'IA qui prend de plus en plus d'importance : les moteurs de recherche .



Qu'est-ce que c'est?

Un assistant personnel basé sur la recherche est un assistant numérique qui utilise la technologie des moteurs de recherche pour aider les utilisateurs dans des tâches telles que la recherche d'informations, la réservation, la définition de rappels et l'envoi de messages. Ces assistants utilisent des algorithmes de recherche pour collecter et analyser des données provenant de diverses sources, en les présentant aux utilisateurs de manière utile et concise.


Google Assistant , Siri , Alexa et Cortana . Ces assistants utilisent efficacement leurs capacités de recherche pour fournir des informations précises et pertinentes, accomplir des tâches et améliorer leurs réponses lorsqu'ils interagissent avec l'utilisateur.


Présentation de Claude d'Anthropic

Anthropic est une organisation de recherche axée sur le développement de systèmes d'IA avancés. Leur dernière création, Claude, est un assistant IA de nouvelle génération conçu pour être utile, honnête et inoffensif. Ce modèle de pointe assure un degré élevé de fiabilité et de prévisibilité dans diverses tâches.


Les principales caractéristiques de Claude incluent :

  • Capacités polyvalentes de conversation et de traitement de texte

  • Maintenir la sécurité et la confidentialité des utilisateurs comme priorité absolue


Les principaux cas d'utilisation de Claude sont :

  • Récapitulation

  • Recherche

  • Ecriture créative et collaborative

  • Questions et réponses

  • Aide au codage


Ces fonctionnalités font de Claude un outil d'IA idéal pour un large éventail d'applications, permettant aux utilisateurs de différents domaines.

Introduction à LangChain

LangChain est un outil polyvalent pour créer des applications de modèles de langage de bout en bout. Il fournit un cadre robuste qui simplifie le processus de création, de gestion et de déploiement de modèles d'apprentissage des langues (LLM). Les LLM sont des modèles d'IA avancés conçus pour comprendre, générer et manipuler du texte de type humain dans diverses langues et tâches.


Les principales fonctionnalités de LangChain incluent :


  • Gestion efficace des invites pour les LLM

  • La possibilité de créer des chaînes de tâches pour des workflows complexes

  • Ajout d'état à l'IA, lui permettant de se souvenir des informations des interactions précédentes


Ces capacités font de LangChain une plate-forme puissante et conviviale pour exploiter le potentiel des modèles de langage dans diverses applications.

Conditions préalables

  • Connaissance de base de Python
  • Connaissance de base de Typescipt et/ou React
  • Accès à l'API Claude d'Anthropic
  • Accès à l'API de recherche Web de SerpApi

Contour

  1. Initialisation du projet
  2. Construire le front-end pour une application d'assistant IA avec Claude et LangCHain
  3. Rédiger les fichiers du projet
  4. Test de l'application AI Assistant

Discussion

Initialisation du projet

app.py (point d'entrée de l'application Flask) 🐍

  1. Installer Flask : Pour commencer, assurez-vous que Flask est installé dans votre environnement. Vous pouvez le faire en utilisant pip :

     pip install Flask


  2. Créer un nouveau répertoire : créez un nouveau répertoire pour votre projet et accédez-y :

     mkdir claude-langchain cd claude-langchain


  3. Configurer un environnement virtuel (facultatif) : il est recommandé d'utiliser un environnement virtuel lorsque vous travaillez avec des projets Python. Vous pouvez en créer un en utilisant venv ou tout autre outil de votre choix :

     python -m venv venv source venv/bin/activate (Linux/Mac) venv\Scripts\activate (Windows)


  4. Créez un fichier main.py : Créez un fichier main.py pour écrire le code de votre application Flask :

     touch app.py # Linux/Mac echo.>app.py # Windows


  5. Ecrivez votre code d'application Flask : Ouvrez le fichier main.py dans votre éditeur de code préféré et ajoutez le code suivant :

     from flask import Flask app = Flask(__name__) @app.route('/') def hello_world(): return 'Hello, World!' if __name__ == '__main__': app.run()


  6. Exécutez l'application Flask : Enregistrez le fichier main.py et exécutez la commande suivante dans votre terminal/invite de commande :

     python main.py


  7. Ouvrez votre navigateur : ouvrez votre navigateur Web préféré et accédez à http://127.0.0.1:5000/ . Vous devriez voir "Hello, World!" affichée sur la page Web.


Et c'est tout! Vous avez initialisé avec succès un projet Flask et créé une application simple.

.env 🌏

  1. Installez python-dotenv et langchain : Pour gérer facilement les variables d'environnement avec un fichier .env , nous utiliserons le package python-dotenv . En même temps, installons également langchain . Installez les deux packages en utilisant pip :

     pip install python-dotenv langchain


  2. Créez un fichier .env : créez un fichier .env dans le répertoire racine de votre projet :

     touch .env # Linux/Mac echo.>.env # Windows


  3. Ajouter des variables d'environnement : Ouvrez le fichier .env dans votre éditeur de code préféré et ajoutez vos variables d'environnement. Chaque variable doit être sur une nouvelle ligne, au format KEY=VALUE :

     ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxxxxxx SERPAPI_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

    Veuillez noter que vous devez disposer à la fois de la clé API pour le modèle Claude d'Anthropic et du service de recherche Web de SerpAPI .


  4. Charger les variables d'environnement : modifiez votre fichier main.py pour charger les variables d'environnement à partir du fichier .env à l'aide de python-dotenv . Mettez à jour votre main.py comme suit :

     import os from flask import Flask from dotenv import load_dotenv load_dotenv() app = Flask(__name__) @app.route('/') def hello_world(): return 'Hello, World!' if __name__ == '__main__': app.run()


  5. Ignorez le fichier .env dans le contrôle de version : il est important de ne pas partager d'informations sensibles comme les clés secrètes dans le contrôle de version. Si vous utilisez Git, ajoutez la ligne suivante à votre fichier .gitignore (créez-en un si vous ne l'avez pas) :

     .env


Maintenant, votre projet Flask est configuré pour utiliser les variables d'environnement du fichier .env . Vous pouvez ajouter plus de variables selon vos besoins et y accéder à l'aide de os.environ.get('KEY') . N'oubliez pas de garder le fichier .env privé et de ne jamais le soumettre au contrôle de version.

Construire le front-end pour une application d'assistant IA avec Claude et LangChain

Ce tutoriel est conçu pour les utilisateurs intermédiaires qui ont une compréhension de base de Node.js, npm, React et Typescript. Nous utiliserons une pile qui inclut ces technologies, ainsi que Tailwind CSS pour le style. Cette pile a été choisie pour sa robustesse, sa polyvalence et le fort soutien de la communauté pour ces technologies. De plus, nous intégrerons le modèle Claude d'Anthropic et LangChain, deux puissantes technologies d'IA qui permettront à notre application de générer des réponses textuelles précises et humaines.

Installer Node.js et NPM

  1. Téléchargez le programme d'installation de Node.js pour votre système d'exploitation à partir du site officiel .

  2. Suivez les invites d'installation pour installer Node.js et npm. La version LTS (Long Term Support) est recommandée pour la plupart des utilisateurs.

  3. Une fois installé, vérifiez l'installation en vérifiant les versions de Node.js et npm depuis votre terminal :

    nœud -v npm -v

Configuration de l'environnement du projet

Installation de l'application Create React

Create React App (CRA) est un utilitaire de ligne de commande qui nous aide à créer une nouvelle application React.js. Nous allons l'installer globalement via npm :

 npm install -g create-react-app
Créer un nouveau projet React avec Typescript

Nous allons utiliser CRA avec le modèle Typescript pour créer un nouveau projet nommé ai-assistant-claude .

 npx create-react-app ai-assistant-claude --template typescript

Cette commande crée un nouveau répertoire appelé ai-assistant-claude dans notre répertoire actuel, hébergeant une nouvelle application React avec prise en charge de Typescript.

Intégration de TailwindCSS

Installation de TailwindCSS

Les étapes suivies dans ce tutoriel sont basées sur la documentation CSS officielle de Tailwind . Reportez-vous à ces documents pour obtenir des instructions plus à jour.

Nous allons commencer par installer TailwindCSS et initialiser la bibliothèque dans notre projet :

 npm install -D tailwindcss npx tailwindcss init
Configuration des chemins de modèle

Ensuite, nous configurons nos chemins de modèles en les ajoutant au fichier tailwind.config.js . Le ++ signifie les lignes que vous allez ajouter :

 /** @type {import('tailwindcss').Config} */ module.exports = { -- content: [], ++ content: [ ++ "./src/**/*.{js,jsx,ts,tsx}", ++ ], theme: { extend: {}, }, plugins: [], }
Ajout de directives Tailwind

Enfin, nous ajouterons les directives @tailwind pour chacune des couches de Tailwind à notre fichier ./src/index.css :

 @tailwind base; @tailwind components; @tailwind utilities;

Et voila ! Tailwind CSS est maintenant intégré à notre projet.

Installation des bibliothèques requises

Avant de passer à la section de codage, finalisons nos préparatifs en installant les bibliothèques nécessaires telles que fontawesome , react-markdown , axios et react-hook-form .

Installation de FontAwesome pour les icônes
 npm i --save @fortawesome/fontawesome-svg-core npm install --save @fortawesome/free-solid-svg-icons npm install --save @fortawesome/react-fontawesome
Installation de React Markdown pour le rendu du contenu Markdown
 npm install --save react-markdown

Une fois ces étapes terminées, votre projet est configuré avec tous les outils et bibliothèques nécessaires. Ensuite, nous commencerons à créer l'application d'assistant IA qui utilise l'API Claude d'Anthropic et LangChain pour générer des réponses textuelles précises et humaines.

Dépannage

Si vous rencontrez des problèmes lors de l'installation ou de la configuration, voici quelques solutions courantes :

  • Pour les problèmes liés à Node.js ou npm, essayez de les réinstaller ou consultez les documents officiels Node.js et npm .
  • Si vous rencontrez des problèmes avec l'application Create React, consultez la documentation de l'ARC pour obtenir de l'aide.
  • Pour les problèmes CSS Tailwind, reportez-vous à la documentation CSS Tailwind .


Pour tout autre problème, consultez la documentation des bibliothèques respectives ou publiez vos problèmes sur StackOverflow ou les référentiels GitHub pertinents.

Rédiger les fichiers du projet

Dans cette section, nous allons revenir à l'application Flask que nous avons initialisée plus tôt et ajouter de nouveaux points de terminaison, tels que /ask et /search . Ceux-ci serviront de points de terminaison pour nos fonctionnalités de chat simples et avancées (ces dernières étant alimentées par les résultats de recherche Google).

Commençons par importer nos modules nécessaires :


 from flask import Flask, jsonify, request from dotenv import load_dotenv from langchain.chat_models import ChatAnthropic from langchain.chains import ConversationChain from langchain.agents import Tool from langchain.agents import AgentType from langchain.utilities import SerpAPIWrapper from langchain.agents import initialize_agent from langchain.memory import ConversationBufferMemory from langchain.prompts.chat import ( ChatPromptTemplate, MessagesPlaceholder, SystemMessagePromptTemplate, AIMessagePromptTemplate, HumanMessagePromptTemplate, ) load_dotenv() app = Flask(__name__)

La section ci-dessus importe tous les packages nécessaires et initialise notre application Flask.

Développer le back-end

Création des points de terminaison de base

Nous allons commencer par créer un point de terminaison de base ( / ) pour tester si notre serveur fonctionne correctement :

 @app.route('/') def hello_world(): return 'Hello, World!'

En visitant l'URL racine, nous devrions recevoir la réponse "Hello, World!", indiquant que notre serveur fonctionne comme prévu.

Création du point de terminaison /ask

Ce point de terminaison traite les messages pour la fonctionnalité de chat de base de notre application. Il lit les données JSON de la requête, traite les messages et génère une réponse à l'aide du modèle Claude d'Anthropic et de LangChain.

 @app.route('/ask', methods=['POST']) def ask_assistant(): # The code for /ask endpoint goes here


Extraction des messages de la requête

Tout d'abord, nous devons vérifier si des données sont fournies et en extraire les messages.

 data = request.get_json() if not data: return jsonify({"error": "No data provided"}), 400 messages = data.get("message")


Génération de la réponse

Le segment de code suivant génère la réponse de chat en utilisant le modèle ChatAnthropic() de LangChain et le ChatPromptTemplate pour structurer notre conversation. L'historique des conversations est stocké à l'aide de ConversationBufferMemory .


 llm = ChatAnthropic() input = "" message_list = [] for message in messages: if message['role'] == 'user': message_list.append( HumanMessagePromptTemplate.from_template(message['content']) ) input = message['content'] elif message['role'] == 'assistant': message_list.append( AIMessagePromptTemplate.from_template(message['content']) ) # Adding SystemMessagePromptTemplate at the beginning of the message_list message_list.insert(0, SystemMessagePromptTemplate.from_template( "The following is a friendly conversation between a human and an AI. The AI is talkative and " "provides lots of specific details from its context. The AI will respond with plain string, replace new lines with \\n which can be easily parsed and stored into JSON, and will try to keep the responses condensed, in as few lines as possible." )) message_list.insert(1, MessagesPlaceholder(variable_name="history")) message_list.insert(-1, HumanMessagePromptTemplate.from_template("{input}")) prompt = ChatPromptTemplate.from_messages(message_list) memory = ConversationBufferMemory(return_messages=True) conversation = ConversationChain(memory=memory, prompt=prompt, llm=llm) result = conversation.predict(input=input)

Envoi de la réponse

Après avoir généré la réponse, nous remplaçons les retours à la ligne dans le résultat et le renvoyons sous la forme d'un objet JSON.

 print(result) return jsonify({"status": "success", "message": result})
Création du point de terminaison /search

Le point de terminaison /search est similaire à /ask mais inclut une fonctionnalité de recherche pour fournir des réponses plus détaillées. Nous utilisons le SerpAPIWrapper pour ajouter cette fonctionnalité.

 @app.route('/search', methods=['POST']) def search_with_assistant(): data = request.get_json() if not data: return jsonify({"error": "No data provided"}), 400 messages = data.get("message") llm = ChatAnthropic() # Get the last message with 'user' role user_messages = [msg for msg in messages if msg['role'] == 'user'] last_user_message = user_messages[-1] if user_messages else None # If there is no user message, return an error response if not last_user_message: return jsonify({"error": "No user message found"}), 400 input = last_user_message['content'] search = SerpAPIWrapper() tools = [ Tool( name = "Current Search", func=search.run, description="useful for when you need to answer questions about current events or the current state of the world" ), ] chat_history = MessagesPlaceholder(variable_name="chat_history") memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True) agent_chain = initialize_agent( tools, llm, agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION, verbose=True, memory=memory, agent_kwargs = { "memory_prompts": [chat_history], "input_variables": ["input", "agent_scratchpad", "chat_history"] } ) result = agent_chain.run(input=input) print(result) return jsonify({"status": "success", "message": result})


Exécution de l'application Flask

Enfin, nous ajoutons le passe-partout standard pour exécuter notre application Flask.

 if __name__ == '__main__': app.run()
Tester le back-end

Si tout se passe bien, voici notre code backend final.

 from flask import Flask, jsonify, request from dotenv import load_dotenv from langchain.chat_models import ChatAnthropic from langchain.chains import ConversationChain from langchain.agents import Tool from langchain.agents import AgentType from langchain.utilities import SerpAPIWrapper from langchain.agents import initialize_agent from langchain.memory import ConversationBufferMemory from langchain.prompts.chat import ( ChatPromptTemplate, MessagesPlaceholder, SystemMessagePromptTemplate, AIMessagePromptTemplate, HumanMessagePromptTemplate, ) load_dotenv() app = Flask(__name__) @app.route('/') def hello_world(): return 'Hello, World!' @app.route('/ask', methods=['POST']) def ask_assistant(): data = request.get_json() if not data: return jsonify({"error": "No data provided"}), 400 messages = data.get("message") llm = ChatAnthropic() input = "" message_list = [] for message in messages: if message['role'] == 'user': message_list.append( HumanMessagePromptTemplate.from_template(message['content']) ) input = message['content'] elif message['role'] == 'assistant': message_list.append( AIMessagePromptTemplate.from_template(message['content']) ) # Adding SystemMessagePromptTemplate at the beginning of the message_list message_list.insert(0, SystemMessagePromptTemplate.from_template( "The following is a friendly conversation between a human and an AI. The AI is talkative and " "provides lots of specific details from its context. The AI will respond with plain string, replace new lines with \\n which can be easily parsed and stored into JSON, and will try to keep the responses condensed, in as few lines as possible." )) message_list.insert(1, MessagesPlaceholder(variable_name="history")) message_list.insert(-1, HumanMessagePromptTemplate.from_template("{input}")) prompt = ChatPromptTemplate.from_messages(message_list) memory = ConversationBufferMemory(return_messages=True) conversation = ConversationChain(memory=memory, prompt=prompt, llm=llm) result = conversation.predict(input=input) print(result) return jsonify({"status": "success", "message": result}) @app.route('/search', methods=['POST']) def search_with_assistant(): data = request.get_json() if not data: return jsonify({"error": "No data provided"}), 400 messages = data.get("message") llm = ChatAnthropic() # Get the last message with 'user' role user_messages = [msg for msg in messages if msg['role'] == 'user'] last_user_message = user_messages[-1] if user_messages else None # If there is no user message, return an error response if not last_user_message: return jsonify({"error": "No user message found"}), 400 input = last_user_message['content'] search = SerpAPIWrapper() tools = [ Tool( name = "Current Search", func=search.run, description="useful for when you need to answer questions about current events or the current state of the world" ), ] chat_history = MessagesPlaceholder(variable_name="chat_history") memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True) agent_chain = initialize_agent( tools, llm, agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION, verbose=True, memory=memory, agent_kwargs = { "memory_prompts": [chat_history], "input_variables": ["input", "agent_scratchpad", "chat_history"] } ) result = agent_chain.run(input=input) print(result) return jsonify({"status": "success", "message": result}) if __name__ == '__main__': app.run()


Maintenant, testons notre application. Exécutez l'application backend avec cette commande, assurez-vous également que notre environnement virtuel est activé.

 flask run


Nous saurons que tout ira bien si notre terminal renvoie cette sortie.


Sans plus tarder, testons nos deux points de terminaison, /ask et /search . Pour faire la distinction entre les deux, envoyons à chacun d'eux une charge utile similaire. Ouvrez votre logiciel de test et de documentation de l'API REST, ou utilisez simplement cURL. Mais dans ce tutoriel, j'utiliserai Insomnia .


Appelons d'abord le point de terminaison /ask avec la charge utile suivante. Disons que je vous ai posé des questions sur la suite du jeu vidéo « The Legend of Zelda : Breath of the Wild ». Ce à quoi il répondra de manière incorrecte. Il faut s'y attendre car le modèle a une date limite de formation à la fin de 2021, dans laquelle il n'y a pas encore eu d'annonces sur la suite.


Qu'en est-il du point de terminaison /search ? Si vous remarquez notre code précédent, ce point de terminaison est géré avec une chaîne plus sophistiquée, qui utilise Agent.


En utilisant Agent, nous pouvons donner à l'IA plus de pouvoir dans la prise de décision, en lui fournissant plus d'outils que son propre modèle, qui, comme nous l'avons déjà démontré, a ses propres défauts.


Pour montrer comment fonctionne le point de terminaison /search , appelons-le avec la même charge utile qu'auparavant.


Cette fois, ça a bien marché ! Comment ça marche sous le capot ? regardons la sortie dans notre terminal.

Chose intéressante, cette fois, le modèle d'IA n'a pas répondu tout de suite, mais a semblé "réfléchir" à ce qu'il fallait faire pour répondre. Il a décidé de répondre après avoir fait l'observation à partir du résultat de la recherche sur le Web que "Sur la base de la recherche d'actualités, il semble qu'une suite intitulée The Legend of Zelda: Tears of the Kingdom ait été annoncée".


Donc, selon cette logique, ne devrions-nous pas simplement utiliser le point de terminaison /search alors ? car il est plus précis, et donc plus intelligent ? Pas assez. Normalement, dans une application basée sur un chatbot, le bot est censé conserver le contexte des conversations précédentes, de sorte qu'il puisse fournir des réponses comme s'il se « souvenait » de tout le contexte de la conversation. Voyons si /search endpoint peut faire exactement cela.


Testons si le point de terminaison /search peut rappeler ce que nous venons de lui demander.


Cela s'est produit parce que, bien que la bibliothèque LangChain ait une fonction de chaîne de mémoire qui peut être utilisée pour conserver les conversations passées, le serveur Web et le service API REST construit dessus sont intrinsèquement sans état. Ce qui signifie que le serveur Web traitera chaque requête comme une nouvelle requête.


Mais n'avons-nous pas inclus les conversations précédentes comme charge utile ? C'est une bonne question. Il s'avère que la chaîne d'agents utilisée dans LangChain ne prend actuellement pas en charge la gestion des invites composées, qui consistent à la fois en demandes et en réponses de l'utilisateur et de l'IA. Nous l'utilisons principalement pour fournir des exemples de conversations pour le modèle, ajustant davantage le modèle à la réponse souhaitée.


L'Agent, quant à lui, travaille en recevant une instruction unique et développe ses chaînes de pensée autour d'elle. C'est pourquoi, quelle que soit la durée de nos conversations, l'agent ne répondra qu'à la dernière demande.


Testons notre point de terminaison /ask pour remarquer la différence.


Cette fois, il a répondu en utilisant nos conversations passées comme contexte supplémentaire ! À présent, nous avons réalisé que nous avions besoin des deux points de terminaison pour créer notre application AI Assistant. Mais comment intégrons-nous à la fois le point de terminaison obsolète mais toujours mémorisé /ask avec le point de terminaison oublieux mais complet et à jour /search ? En construisant le front-end, bien sûr !

Développer le front-end


App.tsx

Revenons au répertoire du projet ai-assistant-claude . Dans ce projet, nous allons commencer à modifier nos composants React, en commençant par le fichier de point d'entrée, App.tsx .

 import React from 'react'; import logo from './logo.svg'; import './App.css'; import { ChatClient } from './ChatClient'; function App() { return ( <div> <ChatClient/> </div> ); } export default App;

Dans l'extrait de code ci-dessus, nous importons le composant ChatClient , qui sera créé dans une étape ultérieure. Nous incluons ensuite le composant <ChatClient/> dans un élément <div> . Cela configure la structure de notre application d'assistant IA à l'aide de composants React.

ChatClient.tsx

 import React, { useState } from 'react'; import { ChatInput } from './ChatInput'; import { ChatHistory } from './ChatHistory'; export interface Message { content: string; role: string; } export const ChatClient: React.FC = () => { const [messages, setMessages] = useState<Array<Message>>([]); const [isLoading, setIsLoading] = useState(false) const handleSimpleChat = (message: string) => { // Send the message and past chat history to the backend // Update messages state with the new message let newMessages = [...messages, { content: message, role: 'user' }] setMessages(newMessages); let postData = { message: newMessages } setIsLoading(true) fetch('/ask', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(postData), }) .then((response) => response.json()) .then((data) => { if (data.status === "success") { setMessages([...newMessages, { content: data.message, role: 'assistant' }]) } setIsLoading(false) console.log('Success:', data); }) .catch((error) => { console.error('Error:', error); setIsLoading(false) }); }; const handleAdvancedChat = (message: string) => { // Trigger AI agent with Google Search functionality // Update messages state with the new message and AI response let newMessages = [...messages, { content: message, role: 'user' }] setMessages(newMessages); let postData = { message: newMessages } setIsLoading(true) fetch('/search', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(postData), }) .then((response) => response.json()) .then((data) => { if (data.status === "success") { setMessages([...newMessages, { content: data.message, role: 'assistant' }]) } console.log('Success:', data); setIsLoading(false) }) .catch((error) => { console.error('Error:', error); setIsLoading(false) }); }; return ( <div className="h-screen bg-gray-100 dark:bg-gray-900 flex items-center justify-center"> <div className='flex flex-col items-center gap-2'> <h1 className='text-white text-xl'>AI Assistant with Claude and LangChain</h1> <div className="w-full max-w-md h-[80vh] bg-white dark:bg-gray-800 rounded-lg shadow-md overflow-hidden flex flex-col"> <ChatHistory messages={messages} isLoading={isLoading} /> <ChatInput onSimpleChat={handleSimpleChat} onAdvancedChat={handleAdvancedChat} /> </div> </div> </div> ); };

Ce composant sert d'interface utilisateur principale pour notre assistant IA. Comme indiqué, il intègre un composant ChatHistory pour afficher l'historique des conversations et un composant ChatInput pour la saisie de texte.


Le composant traite l'entrée du composant ChatInput , envoie des requêtes au serveur principal à l'aide de cette entrée, puis affiche un état de chargement. Si la demande est traitée avec succès, le composant affichera également la réponse reçue du backend.

ChatHistory.tsx

 import React from 'react'; import { ReactMarkdown } from 'react-markdown/lib/react-markdown'; import { Message } from './ChatClient'; interface ChatHistoryProps { messages: Array<Message>; isLoading: boolean } export const ChatHistory: React.FC<ChatHistoryProps> = ({ messages, isLoading }) => { return ( <div className="p-4 h-full overflow-y-auto"> {messages.map((message, index) => ( <div key={index} className={`mb-3 ${ message.role === 'user' ? 'text-right' : 'text-left' }`} > <ReactMarkdown className={`inline-block px-3 py-2 rounded-md ${ index % 2 === 0 ? 'bg-gray-300 dark:bg-gray-700' : 'bg-blue-200 dark:bg-blue-900' }`} > {message.content} </ReactMarkdown> </div> ))} {isLoading && ( <div className="mb-3 text-left"> <ReactMarkdown className="inline-block px-3 py-2 rounded-md bg-blue-200 dark:bg-blue-900 animate-pulse" > {/* Put your desired loading content here */} Thinking... </ReactMarkdown> </div> )} </div> ); };

Heureusement, TailwindCSS propose des classes utilitaires intégrées pour des animations simples, telles que animate-pulse . Cette classe aide élégamment à indiquer qu'une requête attend une réponse. Dans ce composant, nous différencions également le positionnement des messages de "l'utilisateur" et de "l'assistant".


ChatInput.tsx

 import React, { useState } from 'react'; interface ChatInputProps { onSimpleChat: (message: string) => void; onAdvancedChat: (message: string) => void; } export const ChatInput: React.FC<ChatInputProps> = ({ onSimpleChat, onAdvancedChat }) => { const [input, setInput] = useState(''); const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => { setInput(event.target.value); }; const handleSubmit = (handler: (message: string) => void) => { handler(input); setInput(''); }; return ( <div className="flex p-4 border-t border-gray-200 dark:border-gray-700"> <input type="text" value={input} onChange={handleInputChange} placeholder="Type your message..." className="flex-grow px-3 py-2 rounded-md bg-gray-200 text-gray-900 dark:bg-gray-700 dark:text-gray-100 focus:outline-none" /> <button onClick={() => handleSubmit(onSimpleChat)} className="ml-2 px-4 py-2 font-semibold text-gray-600 bg-white dark:text-gray-400 dark:bg-gray-800 border border-gray-300 rounded-md hover:bg-gray-200 dark:hover:bg-gray-700 focus:outline-none" > Ask </button> <button onClick={() => handleSubmit(onAdvancedChat)} className="ml-2 px-4 py-2 font-semibold text-white bg-blue-500 border border-blue-600 rounded-md hover:bg-blue-400 focus:outline-none" > Ask and Search </button> </div> ); };

Enfin, nous avons ajouté deux boutons à notre saisie de texte. Le premier bouton est utilisé pour envoyer l'entrée au point de terminaison /ask , qui traite l'entrée à l'aide du modèle AI sans aucune amélioration supplémentaire.


Ce point de terminaison est sensible au contexte. Le deuxième bouton, nommé à juste titre "Demander et rechercher", envoie l'entrée au point de terminaison /search . En plus de traiter l'entrée via le modèle d'IA, ce bouton déclenche également une recherche sur le Web basée sur l'IA si la situation l'exige.


index.html

 <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="theme-color" content="#000000" /> <meta name="description" content="Web site created using create-react-app" /> <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> <!-- manifest.json provides metadata used when your web app is installed on a user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/ --> <link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> <!-- Notice the use of %PUBLIC_URL% in the tags above. It will be replaced with the URL of the `public` folder during the build. Only files inside the `public` folder can be referenced from the HTML. Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> -- <title>React App</title> ++ <title>Claude AI Assistant</title> </head> <body> <noscript>You need to enable JavaScript to run this app.</noscript> <div id="root"></div> <!-- This HTML file is a template. If you open it directly in the browser, you will see an empty page. You can add webfonts, meta tags, or analytics to this file. The build step will place the bundled scripts into the <body> tag. To begin the development, run `npm start` or `yarn start`. To create a production bundle, use `npm run build` or `yarn build`. --> </body> </html>

Pour finir, nous mettons à jour le titre de notre application dans la page index.html en le changeant de " React App " à " Claude AI Assistant ".


package.json

 { "name": "ai-assistant-claude", "version": "0.1.0", "private": true, ++ "proxy": "http://localhost:5000", "dependencies": {

Enfin, nous ajoutons une configuration de proxy au fichier package.json et le définissons sur http://localhost:5000 . Cela nous aide à contourner les limitations CORS résultant de l'utilisation de différents ports.

Test de l'application AI Assistant

Pour commencer les tests, assurez-vous d'abord que l'application backend (claude-langchain) est déjà en cours d'exécution.


Ensuite, changez le répertoire vers l'application frontale (ai-assistant-claude) et démarrez l'application à l'aide de la commande suivante :

 npm start


La création de l'application peut prendre un moment. Une fois prêt, il s'ouvrira automatiquement dans votre navigateur à localhost:3000 .


Testons à la fois la sensibilité au contexte et la fonctionnalité de recherche ! Tout d'abord, renseignons-nous sur un autre jeu vidéo dont la sortie n'a pas encore été annoncée en 2021 - la suite du dernier jeu Yakuza de SEGA . De plus, nous vous demanderons si ce jeu présente le personnage bien-aimé Kazuma Kiryu des jeux précédents. Pour cela, cliquez sur le bouton « Demander ».


Donnez-lui quelques secondes pour réfléchir...



Étonnamment, l'IA a mal répondu. Yakuza: Like a Dragon est en effet le dernier jeu Yakuza mais met en vedette un protagoniste différent, Ichiban Kasuga. Reformulons la question et utilisons cette fois le bouton « Demander et rechercher ».



Désormais, l'IA a mis plus de temps à décider si elle devait effectuer des recherches sur le Web. Après avoir déterminé qu'une recherche sur le Web était nécessaire et avoir trouvé une réponse satisfaisante, il a renvoyé les informations au client/frontal.


Cette fois, il renvoie le titre "Like a Dragon Gaiden: The Man Who Erased His Name", qui présente en effet Kazuma Kiryu comme protagoniste.


Fascinant, n'est-ce pas ? L'agent, propulsé par un Large Language Model, prédit quelles actions sont nécessaires compte tenu des ressources disponibles (recherche) et fournit une réponse satisfaisante. LangChain facilite la connexion et la "chaîne" du modèle, des invites (instructions au modèle) et d'autres fonctionnalités telles que les agents et les outils.

Conclusion

Nous espérons que vous avez aimé créer cette application et en apprendre davantage sur ces technologies. Pour ceux qui souhaitent plonger plus profondément, vous pouvez accéder au projet terminé pour le front-end et le backend .


Et si vous souhaitez construire avec les dernières technologies réalisées par nos partenaires, rejoignez-nous au prochain défi IA !


*please see lablab.ai for all terms and conditions