from pydantic import BaseModel, Field
from typing import Callable, Awaitable, Any, Optional, List
import re
from difflib import get_close_matches
from open_webui.apps.webui.models.users import Users
from open_webui.apps.webui.models.tools import Tools
from open_webui.utils.misc import get_last_user_message
class Filter:
class Valves(BaseModel):
status: bool = Field(default=False)
match_threshold: int = Field(
default=1
) # Seuil par défaut pour le nombre de mots correspondants
symbols: list = Field(default=["?"]) # Liste par défaut des symboles
fuzzy_match_threshold: float = Field(
default=0.6
) # Seuil pour la correspondance floue
def __init__(self):
self.valves = self.Valves()
self.custom_tools = [] # Liste pour stocker les outils personnalisés
# Ajout manuel d'outils
self.add_custom_tools()
def add_custom_tools(self):
# Ajouter manuellement des outils
self.custom_tools.append(
{
"id": "youtube_video_transcript",
"description": "Outil de transcription de lien youtube à l'aide de liens url.",
}
)
self.custom_tools.append(
{
"id": "web_search",
"description": "Web Search qui utilise SearXNG et Scrap les premières pages trouvées.",
}
)
async def evaluate_needs(self, user_message: str) -> List[str]:
# Évaluer les besoins et intentions de l'utilisateur
user_message_lower = user_message.lower()
needs = []
if "data" in user_message_lower:
needs.append("analyser")
needs.append("visualiser")
if "image" in user_message_lower:
needs.append("modifier")
needs.append("créer")
if "recherche" in user_message_lower or "chercher" in user_message_lower:
needs.append("recherche web")
if "transcription" in user_message_lower or "youtube" in user_message_lower:
needs.append("transcription youtube")
if not needs:
needs.append("aider") # Besoin générique
return needs
async def inlet(
self,
body: dict,
__event_emitter__: Callable[[Any], Awaitable[None]],
__user__: Optional[dict] = None,
__model__: Optional[dict] = None,
) -> dict:
messages = body["messages"]
user_message = get_last_user_message(messages)
if self.valves.status:
await __event_emitter__(
{
"type": "status",
"data": {
"description": "Finding the right tools...",
"done": False,
},
}
)
# Combiner les outils personnalisés et les outils disponibles
all_tools = self.custom_tools + [
{"id": tool.id, "description": tool.meta.description}
for tool in Tools.get_tools()
]
available_tool_ids = (
__model__.get("info", {}).get("meta", {}).get("toolIds", [])
)
available_tools = [
tool for tool in all_tools if tool["id"] in available_tool_ids
]
# Évaluer les besoins de l'utilisateur à l'aide du LLM
user_needs = await self.evaluate_needs(user_message)
# Check each word and symbol in user_message against tool descriptions
user_words = re.findall(r"\w+", user_message.lower())
user_symbols = [char for char in user_message if char in self.valves.symbols]
matched_tool_ids = []
for tool in available_tools:
description_words = re.findall(r"\w+", tool["description"].lower())
description_symbols = [
char for char in tool["description"] if char in self.valves.symbols
]
# Compter les correspondances
match_count = sum(
1 for word in user_words if word in description_words
) + sum(1 for symbol in user_symbols if symbol in description_symbols)
# Correspondance floue : trouver des correspondances proches pour la description
for word in user_words:
close_matches = get_close_matches(
word,
description_words,
n=1,
cutoff=self.valves.fuzzy_match_threshold,
)
match_count += len(close_matches)
# Évaluer la pertinence de l'outil en fonction des besoins
relevance_score = sum(1 for need in user_needs if need in description_words)
# Si l'outil correspond aux besoins ou dépasse le seuil de correspondance
if match_count >= self.valves.match_threshold or relevance_score > 0:
matched_tool_ids.append(tool["id"])
# Lancer automatiquement des outils basés sur les besoins
if "recherche web" in user_needs:
matched_tool_ids.append("web_search")
if "transcription youtube" in user_needs:
matched_tool_ids.append("youtube_video_transcript")
if matched_tool_ids:
body["tool_ids"] = matched_tool_ids
if self.valves.status:
await __event_emitter__(
{
"type": "status",
"data": {
"description": f"Found matching tools: {', '.join(matched_tool_ids)}",
"done": True,
},
}
)
if __event_emitter__:
await __event_emitter__(
{
"type": "status",
"data": {
"description": f"List of tools chosen: {', '.join(matched_tool_ids)}",
"done": True,
},
}
)
else:
if self.valves.status:
await __event_emitter__(
{
"type": "status",
"data": {
"description": "No matching tools found.",
"done": True,
},
}
)
return body