first commit
Browse files- .env-example +6 -0
- README.md +44 -0
- agent.py +188 -0
- app.py +48 -20
- config.py +30 -0
- requirements.txt +7 -1
- tools/__init__.py +10 -0
- tools/web_tools.py +98 -0
.env-example
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Configurez vos clés API ici et renommez ce fichier en .env
|
2 |
+
OPENAI_API_KEY=votre_clé_api_openai_ici
|
3 |
+
|
4 |
+
# Autres configurations
|
5 |
+
# TEMPERATURE=0.7
|
6 |
+
# MAX_TOKENS=4096
|
README.md
CHANGED
@@ -12,4 +12,48 @@ hf_oauth: true
|
|
12 |
hf_oauth_expiration_minutes: 480
|
13 |
---
|
14 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
15 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
12 |
hf_oauth_expiration_minutes: 480
|
13 |
---
|
14 |
|
15 |
+
# Agent IA avancé avec LangChain
|
16 |
+
|
17 |
+
Ce projet implémente un agent IA avancé utilisant LangChain et des outils personnalisés pour répondre aux questions de manière intelligente.
|
18 |
+
|
19 |
+
## Fonctionnalités
|
20 |
+
|
21 |
+
- **Agent basé sur LangChain**: Utilise l'architecture d'agent de LangChain pour une réponse structurée et itérative aux questions
|
22 |
+
- **Outils intégrés**: Recherche web, récupération de contenu web, calcul, parsing JSON, et plus
|
23 |
+
- **Interface Gradio**: Interface utilisateur intuitive pour tester l'agent et soumettre des réponses
|
24 |
+
- **Mémorisation des conversations**: L'agent maintient un historique des interactions
|
25 |
+
|
26 |
+
## Configuration
|
27 |
+
|
28 |
+
1. Clonez ce dépôt
|
29 |
+
2. Installez les dépendances: `pip install -r requirements.txt`
|
30 |
+
3. Copiez `.env-example` en `.env` et configurez votre clé API OpenAI
|
31 |
+
|
32 |
+
## Structure du projet
|
33 |
+
|
34 |
+
- `app.py`: Point d'entrée principal de l'application avec l'interface Gradio
|
35 |
+
- `agent.py`: Implémentation de l'agent avancé avec LangChain
|
36 |
+
- `config.py`: Configuration du projet
|
37 |
+
- `tools/`: Dossier contenant les outils personnalisés pour l'agent
|
38 |
+
- `web_tools.py`: Outils pour la recherche web et la récupération de contenu
|
39 |
+
- `utils.py`: Outils utilitaires (date, calculatrice, parsing JSON)
|
40 |
+
|
41 |
+
## Utilisation
|
42 |
+
|
43 |
+
1. Lancez l'application: `python app.py`
|
44 |
+
2. Connectez-vous avec votre compte Hugging Face
|
45 |
+
3. Utilisez l'onglet "Test de l'agent" pour tester des questions individuelles
|
46 |
+
4. Utilisez l'onglet "Évaluation complète" pour soumettre toutes les réponses
|
47 |
+
|
48 |
+
## Personnalisation
|
49 |
+
|
50 |
+
Vous pouvez personnaliser l'agent en:
|
51 |
+
- Ajoutant de nouveaux outils dans le dossier `tools/`
|
52 |
+
- Modifiant le message système dans `config.py`
|
53 |
+
- Ajustant les paramètres de génération (température, etc.)
|
54 |
+
|
55 |
+
---
|
56 |
+
|
57 |
+
Créé pour le cours Hugging Face Agent Course - Final Assignment
|
58 |
+
|
59 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
agent.py
ADDED
@@ -0,0 +1,188 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import logging
|
3 |
+
from typing import List, Optional, Dict, Any
|
4 |
+
|
5 |
+
from langchain.agents import AgentExecutor
|
6 |
+
from langchain.agents.openai_functions_agent.base import OpenAIFunctionsAgent
|
7 |
+
from langchain.schema import SystemMessage, HumanMessage
|
8 |
+
from langchain.prompts import MessagesPlaceholder
|
9 |
+
from langchain_openai import ChatOpenAI
|
10 |
+
from langchain.memory import ConversationBufferMemory
|
11 |
+
from langchain_core.messages import AIMessage
|
12 |
+
from langchain.callbacks.manager import CallbackManager
|
13 |
+
from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
|
14 |
+
|
15 |
+
from tools import WebSearchTool, WebContentTool, CurrentDateTool, JsonParserTool, CalculatorTool
|
16 |
+
import config
|
17 |
+
|
18 |
+
# Configuration du logging
|
19 |
+
logging.basicConfig(
|
20 |
+
level=logging.INFO,
|
21 |
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
22 |
+
handlers=[logging.StreamHandler()]
|
23 |
+
)
|
24 |
+
logger = logging.getLogger("Agent")
|
25 |
+
|
26 |
+
class AdvancedAgent:
|
27 |
+
"""
|
28 |
+
Agent avancé utilisant LangChain et des outils personnalisés
|
29 |
+
pour répondre aux questions de manière plus intelligente.
|
30 |
+
"""
|
31 |
+
|
32 |
+
def __init__(self,
|
33 |
+
model_name: str = config.DEFAULT_MODEL,
|
34 |
+
api_key: Optional[str] = None,
|
35 |
+
temperature: float = config.TEMPERATURE,
|
36 |
+
system_message: str = config.DEFAULT_SYSTEM_MESSAGE,
|
37 |
+
verbose: bool = True):
|
38 |
+
"""
|
39 |
+
Initialise l'agent avec ses outils et sa configuration.
|
40 |
+
|
41 |
+
Args:
|
42 |
+
model_name: Nom du modèle à utiliser
|
43 |
+
api_key: Clé API OpenAI (prend celle de l'environnement si non spécifiée)
|
44 |
+
temperature: Valeur de température pour la génération de texte
|
45 |
+
system_message: Message système pour l'agent
|
46 |
+
verbose: Afficher les logs détaillés
|
47 |
+
"""
|
48 |
+
self.model_name = model_name
|
49 |
+
self.temperature = temperature
|
50 |
+
self.system_message = system_message
|
51 |
+
self.verbose = verbose
|
52 |
+
|
53 |
+
# Utilise la clé API fournie ou celle des variables d'environnement
|
54 |
+
self.api_key = api_key or config.OPENAI_API_KEY
|
55 |
+
if not self.api_key:
|
56 |
+
logger.warning("Aucune clé API OpenAI trouvée. Certaines fonctionnalités peuvent ne pas fonctionner.")
|
57 |
+
|
58 |
+
# Initialisation du modèle
|
59 |
+
callback_manager = CallbackManager([StreamingStdOutCallbackHandler()]) if verbose else None
|
60 |
+
self.llm = ChatOpenAI(
|
61 |
+
model=self.model_name,
|
62 |
+
temperature=self.temperature,
|
63 |
+
api_key=self.api_key,
|
64 |
+
verbose=self.verbose,
|
65 |
+
callback_manager=callback_manager
|
66 |
+
)
|
67 |
+
|
68 |
+
# Charger les outils
|
69 |
+
self.tools = self._setup_tools()
|
70 |
+
|
71 |
+
# Initialiser la mémoire
|
72 |
+
self.memory = ConversationBufferMemory(
|
73 |
+
memory_key="chat_history",
|
74 |
+
return_messages=True
|
75 |
+
)
|
76 |
+
|
77 |
+
# Créer l'agent
|
78 |
+
self.agent = self._create_agent()
|
79 |
+
|
80 |
+
def _setup_tools(self) -> List:
|
81 |
+
"""Configure et retourne les outils disponibles pour l'agent"""
|
82 |
+
tools = []
|
83 |
+
|
84 |
+
# Ajouter les outils selon la configuration
|
85 |
+
if config.ENABLE_WEB_SEARCH:
|
86 |
+
tools.append(WebSearchTool())
|
87 |
+
tools.append(WebContentTool())
|
88 |
+
|
89 |
+
if config.ENABLE_CALCULATOR:
|
90 |
+
tools.append(CalculatorTool())
|
91 |
+
|
92 |
+
if config.ENABLE_DATE_TOOL:
|
93 |
+
tools.append(CurrentDateTool())
|
94 |
+
|
95 |
+
# Toujours ajouter l'outil JSON parser
|
96 |
+
tools.append(JsonParserTool())
|
97 |
+
|
98 |
+
logger.info(f"Agent initialisé avec {len(tools)} outils")
|
99 |
+
return tools
|
100 |
+
|
101 |
+
def _create_agent(self) -> AgentExecutor:
|
102 |
+
"""Crée et configure l'exécuteur d'agent"""
|
103 |
+
|
104 |
+
# Définir le prompt
|
105 |
+
prompt = [
|
106 |
+
SystemMessage(content=self.system_message),
|
107 |
+
MessagesPlaceholder(variable_name="chat_history"),
|
108 |
+
HumanMessage(content="{input}"),
|
109 |
+
MessagesPlaceholder(variable_name="agent_scratchpad"),
|
110 |
+
]
|
111 |
+
|
112 |
+
# Créer l'agent
|
113 |
+
agent = OpenAIFunctionsAgent(
|
114 |
+
llm=self.llm,
|
115 |
+
tools=self.tools,
|
116 |
+
prompt=prompt
|
117 |
+
)
|
118 |
+
|
119 |
+
# Créer l'exécuteur d'agent
|
120 |
+
agent_executor = AgentExecutor(
|
121 |
+
agent=agent,
|
122 |
+
tools=self.tools,
|
123 |
+
memory=self.memory,
|
124 |
+
verbose=self.verbose,
|
125 |
+
max_iterations=10,
|
126 |
+
early_stopping_method="generate"
|
127 |
+
)
|
128 |
+
|
129 |
+
return agent_executor
|
130 |
+
|
131 |
+
def __call__(self, question: str) -> str:
|
132 |
+
"""
|
133 |
+
Répond à une question en utilisant l'agent.
|
134 |
+
|
135 |
+
Args:
|
136 |
+
question: La question à laquelle répondre
|
137 |
+
|
138 |
+
Returns:
|
139 |
+
La réponse de l'agent
|
140 |
+
"""
|
141 |
+
if not question.strip():
|
142 |
+
return "Veuillez poser une question."
|
143 |
+
|
144 |
+
try:
|
145 |
+
logger.info(f"Question reçue: {question[:50]}...")
|
146 |
+
|
147 |
+
# Exécuter l'agent
|
148 |
+
response = self.agent.invoke({"input": question})
|
149 |
+
|
150 |
+
# Extraire la réponse
|
151 |
+
answer = response.get("output", "Je n'ai pas pu générer de réponse.")
|
152 |
+
|
153 |
+
logger.info(f"Réponse générée: {answer[:50]}...")
|
154 |
+
return answer
|
155 |
+
|
156 |
+
except Exception as e:
|
157 |
+
logger.error(f"Erreur lors du traitement de la question: {str(e)}")
|
158 |
+
return f"Désolé, une erreur s'est produite: {str(e)}"
|
159 |
+
|
160 |
+
def reset_memory(self):
|
161 |
+
"""Réinitialise la mémoire de l'agent"""
|
162 |
+
self.memory.clear()
|
163 |
+
logger.info("Mémoire de l'agent réinitialisée")
|
164 |
+
|
165 |
+
def get_last_interactions(self, count: int = 5) -> List[Dict[str, Any]]:
|
166 |
+
"""
|
167 |
+
Retourne les dernières interactions de l'agent
|
168 |
+
|
169 |
+
Args:
|
170 |
+
count: Nombre d'interactions à retourner
|
171 |
+
|
172 |
+
Returns:
|
173 |
+
Liste des dernières interactions
|
174 |
+
"""
|
175 |
+
history = self.memory.chat_memory.messages
|
176 |
+
|
177 |
+
interactions = []
|
178 |
+
for i in range(0, len(history), 2):
|
179 |
+
if i + 1 < len(history):
|
180 |
+
human_msg = history[i].content if hasattr(history[i], 'content') else str(history[i])
|
181 |
+
ai_msg = history[i+1].content if hasattr(history[i+1], 'content') else str(history[i+1])
|
182 |
+
|
183 |
+
interactions.append({
|
184 |
+
"question": human_msg,
|
185 |
+
"answer": ai_msg
|
186 |
+
})
|
187 |
+
|
188 |
+
return interactions[-count:]
|
app.py
CHANGED
@@ -3,23 +3,34 @@ import gradio as gr
|
|
3 |
import requests
|
4 |
import inspect
|
5 |
import pandas as pd
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6 |
|
7 |
-
# (Keep Constants as is)
|
8 |
# --- Constants ---
|
9 |
-
DEFAULT_API_URL =
|
10 |
-
|
11 |
-
#
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
|
|
|
|
|
|
|
|
23 |
"""
|
24 |
Fetches all questions, runs the BasicAgent on them, submits all answers,
|
25 |
and displays the results.
|
@@ -38,13 +49,15 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
|
|
38 |
questions_url = f"{api_url}/questions"
|
39 |
submit_url = f"{api_url}/submit"
|
40 |
|
41 |
-
# 1. Instantiate Agent
|
42 |
try:
|
43 |
-
|
|
|
44 |
except Exception as e:
|
45 |
print(f"Error instantiating agent: {e}")
|
46 |
return f"Error initializing agent: {e}", None
|
47 |
-
|
|
|
48 |
agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
|
49 |
print(agent_code)
|
50 |
|
@@ -140,9 +153,21 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
|
|
140 |
return status_message, results_df
|
141 |
|
142 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
143 |
# --- Build Gradio Interface using Blocks ---
|
144 |
with gr.Blocks() as demo:
|
145 |
-
gr.Markdown("#
|
146 |
gr.Markdown(
|
147 |
"""
|
148 |
**Instructions:**
|
@@ -192,5 +217,8 @@ if __name__ == "__main__":
|
|
192 |
|
193 |
print("-"*(60 + len(" App Starting ")) + "\n")
|
194 |
|
195 |
-
|
|
|
|
|
|
|
196 |
demo.launch(debug=True, share=False)
|
|
|
3 |
import requests
|
4 |
import inspect
|
5 |
import pandas as pd
|
6 |
+
from dotenv import load_dotenv
|
7 |
+
|
8 |
+
# Importer notre agent avancé
|
9 |
+
from agent import AdvancedAgent
|
10 |
+
import config
|
11 |
+
|
12 |
+
# Charger les variables d'environnement
|
13 |
+
load_dotenv()
|
14 |
|
|
|
15 |
# --- Constants ---
|
16 |
+
DEFAULT_API_URL = config.DEFAULT_API_URL
|
17 |
+
|
18 |
+
# Initialiser l'agent
|
19 |
+
agent = None
|
20 |
+
|
21 |
+
def initialize_agent():
|
22 |
+
"""Initialise l'agent s'il n'est pas déjà initialisé"""
|
23 |
+
global agent
|
24 |
+
if agent is None:
|
25 |
+
try:
|
26 |
+
agent = AdvancedAgent(verbose=False)
|
27 |
+
return True
|
28 |
+
except Exception as e:
|
29 |
+
print(f"Erreur lors de l'initialisation de l'agent: {e}")
|
30 |
+
return False
|
31 |
+
return True
|
32 |
+
|
33 |
+
def run_and_submit_all(profile: gr.OAuthProfile | None):
|
34 |
"""
|
35 |
Fetches all questions, runs the BasicAgent on them, submits all answers,
|
36 |
and displays the results.
|
|
|
49 |
questions_url = f"{api_url}/questions"
|
50 |
submit_url = f"{api_url}/submit"
|
51 |
|
52 |
+
# 1. Instantiate Agent
|
53 |
try:
|
54 |
+
if not initialize_agent():
|
55 |
+
return "Impossible d'initialiser l'agent. Vérifiez les logs pour plus d'informations.", None
|
56 |
except Exception as e:
|
57 |
print(f"Error instantiating agent: {e}")
|
58 |
return f"Error initializing agent: {e}", None
|
59 |
+
|
60 |
+
# In the case of an app running as a hugging Face space, this link points toward your codebase
|
61 |
agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
|
62 |
print(agent_code)
|
63 |
|
|
|
153 |
return status_message, results_df
|
154 |
|
155 |
|
156 |
+
# Fonction pour tester l'agent sur une seule question
|
157 |
+
def test_agent(question):
|
158 |
+
if not initialize_agent():
|
159 |
+
return "Impossible d'initialiser l'agent. Vérifiez les logs pour plus d'informations."
|
160 |
+
|
161 |
+
try:
|
162 |
+
answer = agent(question)
|
163 |
+
return answer
|
164 |
+
except Exception as e:
|
165 |
+
return f"Erreur: {str(e)}"
|
166 |
+
|
167 |
+
|
168 |
# --- Build Gradio Interface using Blocks ---
|
169 |
with gr.Blocks() as demo:
|
170 |
+
gr.Markdown("# Agent avec LangChain - Évaluation")
|
171 |
gr.Markdown(
|
172 |
"""
|
173 |
**Instructions:**
|
|
|
217 |
|
218 |
print("-"*(60 + len(" App Starting ")) + "\n")
|
219 |
|
220 |
+
# Initialiser l'agent au démarrage
|
221 |
+
initialize_agent()
|
222 |
+
|
223 |
+
print("Launching Gradio Interface for Advanced Agent Evaluation...")
|
224 |
demo.launch(debug=True, share=False)
|
config.py
ADDED
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
from dotenv import load_dotenv
|
3 |
+
|
4 |
+
# Charger les variables d'environnement
|
5 |
+
load_dotenv()
|
6 |
+
|
7 |
+
# Configuration de l'API
|
8 |
+
DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
|
9 |
+
|
10 |
+
# Configuration de l'agent
|
11 |
+
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "")
|
12 |
+
DEFAULT_MODEL = "gpt-3.5-turbo" # Modèle par défaut
|
13 |
+
|
14 |
+
# Configurations diverses
|
15 |
+
MAX_TOKENS = 4096
|
16 |
+
TEMPERATURE = 0.7
|
17 |
+
RETRY_ATTEMPTS = 3
|
18 |
+
|
19 |
+
# Configuration des outils
|
20 |
+
ENABLE_WEB_SEARCH = True
|
21 |
+
ENABLE_CALCULATOR = True
|
22 |
+
ENABLE_DATE_TOOL = True
|
23 |
+
|
24 |
+
# Message système par défaut pour l'agent
|
25 |
+
DEFAULT_SYSTEM_MESSAGE = """You are a general AI assistant. I will ask you a question.
|
26 |
+
Report your thoughts, and finish your answer with the following template: FINAL ANSWER: [YOUR FINAL ANSWER].
|
27 |
+
YOUR FINAL ANSWER should be a number OR as few words as possible OR a comma separated list of numbers and/or strings.
|
28 |
+
If you are asked for a number, don't use comma to write your number neither use units such as $ or percent sign unless specified otherwise.
|
29 |
+
If you are asked for a string, don't use articles, neither abbreviations (e.g. for cities), and write the digits in plain text unless specified otherwise.
|
30 |
+
If you are asked for a comma separated list, apply the above rules depending of whether the element to be put in the list is a number or a string. """
|
requirements.txt
CHANGED
@@ -1,2 +1,8 @@
|
|
1 |
gradio
|
2 |
-
requests
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
gradio
|
2 |
+
requests
|
3 |
+
langchain
|
4 |
+
langchain-openai
|
5 |
+
langchain-core
|
6 |
+
python-dotenv
|
7 |
+
bs4
|
8 |
+
pandas
|
tools/__init__.py
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from tools.web_tools import WebSearchTool, WebContentTool
|
2 |
+
from tools.utils import CurrentDateTool, JsonParserTool, CalculatorTool
|
3 |
+
|
4 |
+
__all__ = [
|
5 |
+
"WebSearchTool",
|
6 |
+
"WebContentTool",
|
7 |
+
"CurrentDateTool",
|
8 |
+
"JsonParserTool",
|
9 |
+
"CalculatorTool"
|
10 |
+
]
|
tools/web_tools.py
ADDED
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import requests
|
2 |
+
from bs4 import BeautifulSoup
|
3 |
+
from typing import List, Dict, Any
|
4 |
+
from langchain.tools import BaseTool
|
5 |
+
|
6 |
+
class WebSearchTool(BaseTool):
|
7 |
+
name = "web_search"
|
8 |
+
description = "Recherche des informations sur le web à partir d'un terme de recherche"
|
9 |
+
|
10 |
+
def _run(self, query: str) -> str:
|
11 |
+
"""Exécute une recherche web et retourne les résultats pertinents"""
|
12 |
+
try:
|
13 |
+
# Cette fonction simule une recherche web
|
14 |
+
# Dans un cas réel, vous pourriez utiliser l'API Google ou Bing
|
15 |
+
headers = {
|
16 |
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
|
17 |
+
}
|
18 |
+
search_url = f"https://www.google.com/search?q={query.replace(' ', '+')}"
|
19 |
+
response = requests.get(search_url, headers=headers)
|
20 |
+
|
21 |
+
if response.status_code != 200:
|
22 |
+
return f"Erreur lors de la recherche: {response.status_code}"
|
23 |
+
|
24 |
+
# Extraction des résultats avec BeautifulSoup
|
25 |
+
soup = BeautifulSoup(response.text, 'html.parser')
|
26 |
+
search_results = []
|
27 |
+
|
28 |
+
# Extrait les titres et descriptions des résultats
|
29 |
+
for result in soup.select("div.g"):
|
30 |
+
title_elem = result.select_one("h3")
|
31 |
+
if not title_elem:
|
32 |
+
continue
|
33 |
+
|
34 |
+
title = title_elem.get_text()
|
35 |
+
link = result.select_one("a")["href"] if result.select_one("a") else ""
|
36 |
+
snippet = result.select_one("div.VwiC3b")
|
37 |
+
description = snippet.get_text() if snippet else ""
|
38 |
+
|
39 |
+
if title and description:
|
40 |
+
search_results.append(f"Titre: {title}\nLien: {link}\nDescription: {description}\n---")
|
41 |
+
|
42 |
+
if not search_results:
|
43 |
+
return "Aucun résultat trouvé pour cette recherche."
|
44 |
+
|
45 |
+
return "\n".join(search_results[:3]) # Limite à 3 résultats
|
46 |
+
|
47 |
+
except Exception as e:
|
48 |
+
return f"Erreur lors de la recherche web: {str(e)}"
|
49 |
+
|
50 |
+
async def _arun(self, query: str) -> str:
|
51 |
+
"""Version asynchrone de l'outil"""
|
52 |
+
# Implémentation asynchrone si nécessaire
|
53 |
+
return self._run(query)
|
54 |
+
|
55 |
+
|
56 |
+
class WebContentTool(BaseTool):
|
57 |
+
name = "fetch_web_content"
|
58 |
+
description = "Récupère le contenu d'une page web à partir d'une URL"
|
59 |
+
|
60 |
+
def _run(self, url: str) -> str:
|
61 |
+
"""Récupère et nettoie le contenu d'une page web"""
|
62 |
+
try:
|
63 |
+
headers = {
|
64 |
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
|
65 |
+
}
|
66 |
+
response = requests.get(url, headers=headers)
|
67 |
+
|
68 |
+
if response.status_code != 200:
|
69 |
+
return f"Erreur lors de la récupération du contenu: {response.status_code}"
|
70 |
+
|
71 |
+
# Extraction du contenu avec BeautifulSoup
|
72 |
+
soup = BeautifulSoup(response.text, 'html.parser')
|
73 |
+
|
74 |
+
# Supprimer les scripts, styles et autres éléments non pertinents
|
75 |
+
for element in soup(['script', 'style', 'header', 'footer', 'nav']):
|
76 |
+
element.decompose()
|
77 |
+
|
78 |
+
# Extraire le texte principal
|
79 |
+
text = soup.get_text(separator='\n')
|
80 |
+
|
81 |
+
# Nettoyer le texte (espaces multiples, lignes vides)
|
82 |
+
lines = [line.strip() for line in text.split('\n') if line.strip()]
|
83 |
+
cleaned_text = '\n'.join(lines)
|
84 |
+
|
85 |
+
# Limiter la longueur du texte retourné
|
86 |
+
max_length = 5000
|
87 |
+
if len(cleaned_text) > max_length:
|
88 |
+
cleaned_text = cleaned_text[:max_length] + "... (contenu tronqué)"
|
89 |
+
|
90 |
+
return cleaned_text
|
91 |
+
|
92 |
+
except Exception as e:
|
93 |
+
return f"Erreur lors de la récupération du contenu web: {str(e)}"
|
94 |
+
|
95 |
+
async def _arun(self, url: str) -> str:
|
96 |
+
"""Version asynchrone de l'outil"""
|
97 |
+
# Implémentation asynchrone si nécessaire
|
98 |
+
return self._run(url)
|