Ajout tools de recherche et structuration agent
Browse files- .env-example +0 -6
- .gitignore +115 -0
- agent.py +62 -117
- app.py +2 -2
- config.py +11 -11
- requirements.txt +8 -2
- tools/__init__.py +10 -8
- tools/audio_tools.py +47 -0
- tools/spreadsheet_tools.py +43 -0
- tools/string_tools.py +35 -0
- tools/web_tools.py +12 -44
.env-example
DELETED
@@ -1,6 +0,0 @@
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.gitignore
ADDED
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Byte-compiled / optimized / DLL files
|
2 |
+
__pycache__/
|
3 |
+
*.py[cod]
|
4 |
+
*$py.class
|
5 |
+
*.so
|
6 |
+
|
7 |
+
# Distribution / packaging
|
8 |
+
.Python
|
9 |
+
build/
|
10 |
+
develop-eggs/
|
11 |
+
dist/
|
12 |
+
downloads/
|
13 |
+
eggs/
|
14 |
+
.eggs/
|
15 |
+
lib/
|
16 |
+
lib64/
|
17 |
+
parts/
|
18 |
+
sdist/
|
19 |
+
var/
|
20 |
+
wheels/
|
21 |
+
*.egg-info/
|
22 |
+
.installed.cfg
|
23 |
+
*.egg
|
24 |
+
|
25 |
+
# Virtual environments
|
26 |
+
venv/
|
27 |
+
ENV/
|
28 |
+
env/
|
29 |
+
.env
|
30 |
+
.venv
|
31 |
+
env.bak/
|
32 |
+
venv.bak/
|
33 |
+
.python-version
|
34 |
+
|
35 |
+
# Unit test / coverage reports
|
36 |
+
htmlcov/
|
37 |
+
.tox/
|
38 |
+
.nox/
|
39 |
+
.coverage
|
40 |
+
.coverage.*
|
41 |
+
.cache
|
42 |
+
nosetests.xml
|
43 |
+
coverage.xml
|
44 |
+
*.cover
|
45 |
+
.hypothesis/
|
46 |
+
.pytest_cache/
|
47 |
+
pytest-*.xml
|
48 |
+
|
49 |
+
# Jupyter Notebook
|
50 |
+
.ipynb_checkpoints
|
51 |
+
|
52 |
+
# IPython
|
53 |
+
profile_default/
|
54 |
+
ipython_config.py
|
55 |
+
|
56 |
+
# Logs
|
57 |
+
*.log
|
58 |
+
logs/
|
59 |
+
log/
|
60 |
+
|
61 |
+
# IDE specific files
|
62 |
+
.idea/
|
63 |
+
.vscode/
|
64 |
+
*.swp
|
65 |
+
*.swo
|
66 |
+
*~
|
67 |
+
.DS_Store
|
68 |
+
.project
|
69 |
+
.pydevproject
|
70 |
+
.settings/
|
71 |
+
.vs/
|
72 |
+
*.sublime-project
|
73 |
+
*.sublime-workspace
|
74 |
+
|
75 |
+
# Database
|
76 |
+
*.db
|
77 |
+
*.rdb
|
78 |
+
*.sqlite
|
79 |
+
*.sqlite3
|
80 |
+
|
81 |
+
# Environment variables
|
82 |
+
.env
|
83 |
+
.env.local
|
84 |
+
.env.development.local
|
85 |
+
.env.test.local
|
86 |
+
.env.production.local
|
87 |
+
|
88 |
+
# macOS specific
|
89 |
+
.DS_Store
|
90 |
+
.AppleDouble
|
91 |
+
.LSOverride
|
92 |
+
Icon
|
93 |
+
._*
|
94 |
+
.DocumentRevisions-V100
|
95 |
+
.fseventsd
|
96 |
+
.Spotlight-V100
|
97 |
+
.TemporaryItems
|
98 |
+
.Trashes
|
99 |
+
.VolumeIcon.icns
|
100 |
+
.com.apple.timemachine.donotpresent
|
101 |
+
|
102 |
+
# AI/model files
|
103 |
+
*.h5
|
104 |
+
*.pb
|
105 |
+
*.onnx
|
106 |
+
*.tflite
|
107 |
+
*.pt
|
108 |
+
*.pth
|
109 |
+
*.weights
|
110 |
+
|
111 |
+
# Temporary files
|
112 |
+
tmp/
|
113 |
+
temp/
|
114 |
+
.tmp
|
115 |
+
*.tmp
|
agent.py
CHANGED
@@ -1,18 +1,18 @@
|
|
1 |
import os
|
2 |
import logging
|
3 |
-
from typing import List,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
|
5 |
-
from
|
6 |
-
|
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
|
@@ -23,61 +23,30 @@ logging.basicConfig(
|
|
23 |
)
|
24 |
logger = logging.getLogger("Agent")
|
25 |
|
26 |
-
class
|
27 |
"""
|
28 |
-
Agent avancé utilisant
|
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 |
-
|
60 |
-
|
61 |
-
|
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 |
-
#
|
72 |
-
self.
|
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 |
|
@@ -86,47 +55,51 @@ class AdvancedAgent:
|
|
86 |
tools.append(WebSearchTool())
|
87 |
tools.append(WebContentTool())
|
88 |
|
89 |
-
if config.
|
90 |
-
|
91 |
|
92 |
-
if config.
|
93 |
-
|
94 |
-
|
95 |
-
#
|
96 |
-
tools.append(
|
97 |
|
98 |
logger.info(f"Agent initialisé avec {len(tools)} outils")
|
99 |
return tools
|
100 |
|
101 |
-
def
|
102 |
-
"""Crée
|
103 |
|
104 |
# Définir le prompt
|
105 |
-
prompt = [
|
106 |
-
|
107 |
MessagesPlaceholder(variable_name="chat_history"),
|
108 |
-
|
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 |
-
#
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
127 |
)
|
|
|
128 |
|
129 |
-
|
|
|
130 |
|
131 |
def __call__(self, question: str) -> str:
|
132 |
"""
|
@@ -144,45 +117,17 @@ class AdvancedAgent:
|
|
144 |
try:
|
145 |
logger.info(f"Question reçue: {question[:50]}...")
|
146 |
|
147 |
-
# Exécuter
|
148 |
-
|
|
|
|
|
149 |
|
150 |
-
# Extraire la réponse
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
return
|
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:]
|
|
|
1 |
import os
|
2 |
import logging
|
3 |
+
from typing import List, Dict, Any, Tuple
|
4 |
+
from langchain.tools import BaseTool
|
5 |
+
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
|
6 |
+
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
|
7 |
+
from langchain_core.output_parsers import StrOutputParser
|
8 |
+
from langchain_core.runnables import RunnablePassthrough
|
9 |
+
from langchain_huggingface import HuggingFaceEndpoint
|
10 |
+
from langgraph.graph import START, StateGraph, MessagesState
|
11 |
+
from langgraph.prebuilt import tools_condition, ToolNode
|
12 |
|
13 |
+
from tools import WebSearchTool, WebContentTool
|
14 |
+
#, AudioToTextTool, SpreadsheetParserTool, StringUtilitiesTool
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
15 |
|
|
|
16 |
import config
|
17 |
|
18 |
# Configuration du logging
|
|
|
23 |
)
|
24 |
logger = logging.getLogger("Agent")
|
25 |
|
26 |
+
class LangGraphAgent:
|
27 |
"""
|
28 |
+
Agent avancé utilisant LangGraph et le modèle Qwen3-30B-A3B
|
|
|
29 |
"""
|
30 |
|
31 |
+
def __init__(self, verbose: bool = True):
|
|
|
|
|
|
|
|
|
|
|
32 |
"""
|
33 |
Initialise l'agent avec ses outils et sa configuration.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
34 |
"""
|
|
|
|
|
|
|
35 |
self.verbose = verbose
|
36 |
|
|
|
|
|
|
|
|
|
|
|
37 |
# Initialisation du modèle
|
38 |
+
self.llm = HuggingFaceEndpoint(
|
39 |
+
endpoint_url=f"https://api-inference.huggingface.co/models/{config.MODEL_NAME}",
|
40 |
+
huggingfacehub_api_token=config.HUGGINGFACE_API_KEY,
|
|
|
|
|
|
|
|
|
41 |
)
|
42 |
|
43 |
# Charger les outils
|
44 |
self.tools = self._setup_tools()
|
45 |
|
46 |
+
# Créer le graphe d'exécution
|
47 |
+
self.workflow = self._create_workflow()
|
|
|
|
|
|
|
|
|
|
|
|
|
48 |
|
49 |
+
def _setup_tools(self) -> List[BaseTool]:
|
50 |
"""Configure et retourne les outils disponibles pour l'agent"""
|
51 |
tools = []
|
52 |
|
|
|
55 |
tools.append(WebSearchTool())
|
56 |
tools.append(WebContentTool())
|
57 |
|
58 |
+
# if config.ENABLE_AUDIO_TO_TEXT:
|
59 |
+
# tools.append(AudioToTextTool())
|
60 |
|
61 |
+
# if config.ENABLE_SPREADSHEET_PARSER:
|
62 |
+
# tools.append(SpreadsheetParserTool())
|
63 |
+
|
64 |
+
# if config.ENABLE_STRING_UTILITIES:
|
65 |
+
# tools.append(StringUtilitiesTool())
|
66 |
|
67 |
logger.info(f"Agent initialisé avec {len(tools)} outils")
|
68 |
return tools
|
69 |
|
70 |
+
def _create_workflow(self) -> StateGraph:
|
71 |
+
"""Crée le graphe d'exécution de l'agent"""
|
72 |
|
73 |
# Définir le prompt
|
74 |
+
prompt = ChatPromptTemplate.from_messages([
|
75 |
+
("system", config.DEFAULT_SYSTEM_MESSAGE),
|
76 |
MessagesPlaceholder(variable_name="chat_history"),
|
77 |
+
("human", "{input}"),
|
78 |
MessagesPlaceholder(variable_name="agent_scratchpad"),
|
79 |
+
])
|
80 |
|
|
|
|
|
|
|
|
|
|
|
|
|
81 |
|
82 |
+
# Définir les nodes
|
83 |
+
def assistant(state: MessagesState):
|
84 |
+
return {"messages": [self.llm.invoke(state["messages"])]}
|
85 |
+
|
86 |
+
# Créer le graphe
|
87 |
+
builder = StateGraph(MessagesState)
|
88 |
+
|
89 |
+
# Ajouter les nodes
|
90 |
+
builder.add_node("assistant", assistant)
|
91 |
+
builder.add_node("tools", ToolNode(self.tools))
|
92 |
+
|
93 |
+
# Ajouter les edges
|
94 |
+
builder.add_edge(START, "assistant")
|
95 |
+
builder.add_conditional_edges(
|
96 |
+
"assistant",
|
97 |
+
tools_condition,
|
98 |
)
|
99 |
+
builder.add_edge("tools", "assistant")
|
100 |
|
101 |
+
# Compiler le graphe
|
102 |
+
return builder.compile()
|
103 |
|
104 |
def __call__(self, question: str) -> str:
|
105 |
"""
|
|
|
117 |
try:
|
118 |
logger.info(f"Question reçue: {question[:50]}...")
|
119 |
|
120 |
+
# Exécuter le workflow
|
121 |
+
result = self.workflow.invoke({
|
122 |
+
"messages": [HumanMessage(content=question)]
|
123 |
+
})
|
124 |
|
125 |
+
# Extraire la réponse finale
|
126 |
+
final_message = result["messages"][-1].content
|
127 |
+
if "FINAL ANSWER:" in final_message:
|
128 |
+
return final_message.split("FINAL ANSWER:")[1].strip()
|
129 |
+
return final_message
|
130 |
|
131 |
except Exception as e:
|
132 |
logger.error(f"Erreur lors du traitement de la question: {str(e)}")
|
133 |
+
return f"Désolé, une erreur s'est produite: {str(e)}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app.py
CHANGED
@@ -6,7 +6,7 @@ import pandas as pd
|
|
6 |
from dotenv import load_dotenv
|
7 |
|
8 |
# Importer notre agent avancé
|
9 |
-
from agent import
|
10 |
import config
|
11 |
|
12 |
# Charger les variables d'environnement
|
@@ -23,7 +23,7 @@ def initialize_agent():
|
|
23 |
global agent
|
24 |
if agent is None:
|
25 |
try:
|
26 |
-
agent =
|
27 |
return True
|
28 |
except Exception as e:
|
29 |
print(f"Erreur lors de l'initialisation de l'agent: {e}")
|
|
|
6 |
from dotenv import load_dotenv
|
7 |
|
8 |
# Importer notre agent avancé
|
9 |
+
from agent import LangGraphAgent
|
10 |
import config
|
11 |
|
12 |
# Charger les variables d'environnement
|
|
|
23 |
global agent
|
24 |
if agent is None:
|
25 |
try:
|
26 |
+
agent = LangGraphAgent(verbose=False)
|
27 |
return True
|
28 |
except Exception as e:
|
29 |
print(f"Erreur lors de l'initialisation de l'agent: {e}")
|
config.py
CHANGED
@@ -7,9 +7,9 @@ load_dotenv()
|
|
7 |
# Configuration de l'API
|
8 |
DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
|
9 |
|
10 |
-
# Configuration
|
11 |
-
|
12 |
-
|
13 |
|
14 |
# Configurations diverses
|
15 |
MAX_TOKENS = 4096
|
@@ -18,13 +18,13 @@ RETRY_ATTEMPTS = 3
|
|
18 |
|
19 |
# Configuration des outils
|
20 |
ENABLE_WEB_SEARCH = True
|
21 |
-
|
22 |
-
|
|
|
23 |
|
24 |
# Message système par défaut pour l'agent
|
25 |
-
DEFAULT_SYSTEM_MESSAGE = """You are a
|
26 |
-
Report your thoughts, and finish your answer with the following template:
|
27 |
-
|
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 |
-
|
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. """
|
|
|
7 |
# Configuration de l'API
|
8 |
DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
|
9 |
|
10 |
+
# Configuration du modèle
|
11 |
+
MODEL_NAME = "Qwen/Qwen3-30B-A3B"
|
12 |
+
HUGGINGFACE_API_KEY = os.getenv("HUGGINGFACE_API_KEY", "")
|
13 |
|
14 |
# Configurations diverses
|
15 |
MAX_TOKENS = 4096
|
|
|
18 |
|
19 |
# Configuration des outils
|
20 |
ENABLE_WEB_SEARCH = True
|
21 |
+
ENABLE_AUDIO_TO_TEXT = False
|
22 |
+
ENABLE_SPREADSHEET_PARSER = False
|
23 |
+
ENABLE_STRING_UTILITIES = False
|
24 |
|
25 |
# Message système par défaut pour l'agent
|
26 |
+
DEFAULT_SYSTEM_MESSAGE = """You are a helpful assistant tasked with answering questions using a set of tools.
|
27 |
+
Now, I will ask you a question. Report your thoughts, and finish your answer with the following template:
|
28 |
+
FINAL ANSWER: [YOUR FINAL ANSWER].
|
29 |
+
YOUR FINAL ANSWER should be a number OR as few words as possible OR a comma separated list of numbers and/or strings. 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. 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. 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.
|
30 |
+
Your answer should only start with "FINAL ANSWER: ", then follows with the answer. """
|
|
requirements.txt
CHANGED
@@ -1,8 +1,14 @@
|
|
1 |
gradio
|
|
|
2 |
requests
|
3 |
langchain
|
4 |
-
langchain-
|
5 |
langchain-core
|
|
|
|
|
6 |
python-dotenv
|
7 |
bs4
|
8 |
-
pandas
|
|
|
|
|
|
|
|
1 |
gradio
|
2 |
+
gradio[oauth]
|
3 |
requests
|
4 |
langchain
|
5 |
+
langchain-community
|
6 |
langchain-core
|
7 |
+
langchain-huggingface
|
8 |
+
langgraph
|
9 |
python-dotenv
|
10 |
bs4
|
11 |
+
pandas
|
12 |
+
openpyxl
|
13 |
+
pydub
|
14 |
+
huggingface-hub
|
tools/__init__.py
CHANGED
@@ -1,10 +1,12 @@
|
|
1 |
-
from
|
2 |
-
from
|
|
|
|
|
3 |
|
4 |
__all__ = [
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
]
|
|
|
1 |
+
from .web_tools import WebSearchTool, WebContentTool
|
2 |
+
# from .audio_tools import AudioToTextTool
|
3 |
+
# from .spreadsheet_tools import SpreadsheetParserTool
|
4 |
+
# from .string_tools import StringUtilitiesTool
|
5 |
|
6 |
__all__ = [
|
7 |
+
'WebSearchTool',
|
8 |
+
'WebContentTool',
|
9 |
+
# 'AudioToTextTool',
|
10 |
+
# 'SpreadsheetParserTool',
|
11 |
+
# 'StringUtilitiesTool'
|
12 |
+
]
|
tools/audio_tools.py
ADDED
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
from typing import Optional
|
3 |
+
from langchain.tools import BaseTool
|
4 |
+
import requests
|
5 |
+
from pydub import AudioSegment
|
6 |
+
import tempfile
|
7 |
+
|
8 |
+
class AudioToTextTool(BaseTool):
|
9 |
+
name = "audio_to_text"
|
10 |
+
description = "Convertit un fichier audio en texte en utilisant l'API Hugging Face"
|
11 |
+
|
12 |
+
def _run(self, audio_path: str) -> str:
|
13 |
+
"""Convertit un fichier audio en texte"""
|
14 |
+
try:
|
15 |
+
# Vérifier si le fichier existe
|
16 |
+
if not os.path.exists(audio_path):
|
17 |
+
return f"Erreur: Le fichier {audio_path} n'existe pas"
|
18 |
+
|
19 |
+
# Convertir le fichier audio en format WAV si nécessaire
|
20 |
+
audio = AudioSegment.from_file(audio_path)
|
21 |
+
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as temp_file:
|
22 |
+
audio.export(temp_file.name, format="wav")
|
23 |
+
temp_path = temp_file.name
|
24 |
+
|
25 |
+
# Appeler l'API Hugging Face pour la transcription
|
26 |
+
API_URL = "https://api-inference.huggingface.co/models/facebook/wav2vec2-large-960h-lv60-self"
|
27 |
+
headers = {"Authorization": f"Bearer {os.getenv('HUGGINGFACE_API_KEY')}"}
|
28 |
+
|
29 |
+
with open(temp_path, "rb") as f:
|
30 |
+
data = f.read()
|
31 |
+
|
32 |
+
response = requests.post(API_URL, headers=headers, data=data)
|
33 |
+
|
34 |
+
# Nettoyer le fichier temporaire
|
35 |
+
os.unlink(temp_path)
|
36 |
+
|
37 |
+
if response.status_code != 200:
|
38 |
+
return f"Erreur lors de la transcription: {response.text}"
|
39 |
+
|
40 |
+
return response.json().get("text", "Aucun texte transcrit")
|
41 |
+
|
42 |
+
except Exception as e:
|
43 |
+
return f"Erreur lors de la conversion audio en texte: {str(e)}"
|
44 |
+
|
45 |
+
async def _arun(self, audio_path: str) -> str:
|
46 |
+
"""Version asynchrone de l'outil"""
|
47 |
+
return self._run(audio_path)
|
tools/spreadsheet_tools.py
ADDED
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pandas as pd
|
2 |
+
from typing import List, Dict, Any
|
3 |
+
from langchain.tools import BaseTool
|
4 |
+
|
5 |
+
class SpreadsheetParserTool(BaseTool):
|
6 |
+
name = "spreadsheet_parser"
|
7 |
+
description = "Analyse un fichier Excel et extrait des informations spécifiques"
|
8 |
+
|
9 |
+
def _run(self, file_path: str, query: str) -> str:
|
10 |
+
"""Analyse un fichier Excel et répond à une requête spécifique"""
|
11 |
+
try:
|
12 |
+
# Lire le fichier Excel
|
13 |
+
df = pd.read_excel(file_path)
|
14 |
+
|
15 |
+
# Analyser la requête pour déterminer l'action à effectuer
|
16 |
+
if "somme" in query.lower() or "total" in query.lower():
|
17 |
+
# Trouver les colonnes numériques
|
18 |
+
numeric_cols = df.select_dtypes(include=['number']).columns
|
19 |
+
if len(numeric_cols) > 0:
|
20 |
+
return f"Somme des colonnes numériques:\n{df[numeric_cols].sum().to_string()}"
|
21 |
+
else:
|
22 |
+
return "Aucune colonne numérique trouvée dans le fichier"
|
23 |
+
|
24 |
+
elif "moyenne" in query.lower() or "average" in query.lower():
|
25 |
+
numeric_cols = df.select_dtypes(include=['number']).columns
|
26 |
+
if len(numeric_cols) > 0:
|
27 |
+
return f"Moyenne des colonnes numériques:\n{df[numeric_cols].mean().to_string()}"
|
28 |
+
else:
|
29 |
+
return "Aucune colonne numérique trouvée dans le fichier"
|
30 |
+
|
31 |
+
elif "premières lignes" in query.lower() or "first rows" in query.lower():
|
32 |
+
return f"Premières lignes du fichier:\n{df.head().to_string()}"
|
33 |
+
|
34 |
+
else:
|
35 |
+
# Par défaut, retourner un résumé du fichier
|
36 |
+
return f"Résumé du fichier:\nNombre de lignes: {len(df)}\nColonnes: {', '.join(df.columns)}\nTypes de données:\n{df.dtypes.to_string()}"
|
37 |
+
|
38 |
+
except Exception as e:
|
39 |
+
return f"Erreur lors de l'analyse du fichier Excel: {str(e)}"
|
40 |
+
|
41 |
+
async def _arun(self, file_path: str, query: str) -> str:
|
42 |
+
"""Version asynchrone de l'outil"""
|
43 |
+
return self._run(file_path, query)
|
tools/string_tools.py
ADDED
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from typing import List, Dict, Any
|
2 |
+
from langchain.tools import BaseTool
|
3 |
+
|
4 |
+
class StringUtilitiesTool(BaseTool):
|
5 |
+
name = "string_utilities"
|
6 |
+
description = "Fournit des utilitaires pour manipuler les chaînes de caractères"
|
7 |
+
|
8 |
+
def _run(self, text: str, operation: str) -> str:
|
9 |
+
"""Effectue une opération sur une chaîne de caractères"""
|
10 |
+
try:
|
11 |
+
if operation == "reverse":
|
12 |
+
return text[::-1]
|
13 |
+
elif operation == "uppercase":
|
14 |
+
return text.upper()
|
15 |
+
elif operation == "lowercase":
|
16 |
+
return text.lower()
|
17 |
+
elif operation == "capitalize":
|
18 |
+
return text.capitalize()
|
19 |
+
elif operation == "title":
|
20 |
+
return text.title()
|
21 |
+
elif operation == "strip":
|
22 |
+
return text.strip()
|
23 |
+
elif operation == "split":
|
24 |
+
return str(text.split())
|
25 |
+
elif operation == "join":
|
26 |
+
return " ".join(text.split())
|
27 |
+
else:
|
28 |
+
return f"Opération non supportée: {operation}. Opérations disponibles: reverse, uppercase, lowercase, capitalize, title, strip, split, join"
|
29 |
+
|
30 |
+
except Exception as e:
|
31 |
+
return f"Erreur lors de la manipulation de la chaîne: {str(e)}"
|
32 |
+
|
33 |
+
async def _arun(self, text: str, operation: str) -> str:
|
34 |
+
"""Version asynchrone de l'outil"""
|
35 |
+
return self._run(text, operation)
|
tools/web_tools.py
CHANGED
@@ -1,61 +1,30 @@
|
|
|
|
|
|
|
|
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 |
-
|
14 |
-
|
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"""
|
@@ -63,7 +32,7 @@ class WebContentTool(BaseTool):
|
|
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}"
|
@@ -94,5 +63,4 @@ class WebContentTool(BaseTool):
|
|
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)
|
|
|
1 |
+
from langchain_community.tools import DuckDuckGoSearchRun
|
2 |
+
from langchain.tools import BaseTool
|
3 |
+
from typing import Optional, Type
|
4 |
import requests
|
5 |
from bs4 import BeautifulSoup
|
|
|
|
|
6 |
|
7 |
class WebSearchTool(BaseTool):
|
8 |
+
name: str = "web_search"
|
9 |
+
description: str = "Recherche des informations sur le web à partir d'un terme de recherche"
|
10 |
+
args_schema: Optional[Type] = None
|
11 |
|
12 |
def _run(self, query: str) -> str:
|
13 |
"""Exécute une recherche web et retourne les résultats pertinents"""
|
14 |
try:
|
15 |
+
search_tool = DuckDuckGoSearchRun()
|
16 |
+
return search_tool.run(query)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
17 |
except Exception as e:
|
18 |
return f"Erreur lors de la recherche web: {str(e)}"
|
19 |
|
20 |
async def _arun(self, query: str) -> str:
|
21 |
"""Version asynchrone de l'outil"""
|
|
|
22 |
return self._run(query)
|
23 |
|
|
|
24 |
class WebContentTool(BaseTool):
|
25 |
+
name: str = "fetch_web_content"
|
26 |
+
description: str = "Récupère le contenu d'une page web à partir d'une URL"
|
27 |
+
args_schema: Optional[Type] = None
|
28 |
|
29 |
def _run(self, url: str) -> str:
|
30 |
"""Récupère et nettoie le contenu d'une page web"""
|
|
|
32 |
headers = {
|
33 |
"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"
|
34 |
}
|
35 |
+
response = requests.get(url, headers=headers, timeout=10)
|
36 |
|
37 |
if response.status_code != 200:
|
38 |
return f"Erreur lors de la récupération du contenu: {response.status_code}"
|
|
|
63 |
|
64 |
async def _arun(self, url: str) -> str:
|
65 |
"""Version asynchrone de l'outil"""
|
|
|
66 |
return self._run(url)
|