Je trouve que mes recherches internet certaines fo...
Créé le : 12 février 2025
Répondu en utilisant O3 Mini High par Chat01
Créé le : 12 février 2025
Répondu en utilisant O3 Mini High par Chat01
Je trouve que mes recherches internet certaines fois manque de pertinence et ne sont pas assez pertinenete je veux dire le llm extrait mal le context des requete et la conversation semble biazzare peux tu me l'améliorer stp pour une meilleur experience utilisateur :
"""
from gevent import monkey
monkey.patch_all()
import os
import re
import json
import logging
from typing import Any, Dict, List, Optional, Tuple, Generator
import time
import requests
from bs4 import BeautifulSoup
from concurrent.futures import ThreadPoolExecutor, as_completed
from flask import Flask, request, Response, jsonify, stream_with_context
from flask_cors import CORS
from groq import Groq
from e2b_code_interpreter import Sandbox
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
import httpx # Pour gérer les exceptions HTTP spécifiques
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(name)
app = Flask(name)
CORS(app)
GROQ_API_KEY = os.environ.get("GROQ_API_KEY")
E2B_API_KEY = os.environ.get("E2B_API_KEY")
PISTON_API_URL = "https://emkc.org/api/v2/piston/execute" # URL de l'API Piston
WHOOGLE_INSTANCE_URL = "https://chatapp-1-u8t2.onrender.com" # URL de votre instance Whoogle
groq_client = Groq(api_key=GROQ_API_KEY)
sandbox = Sandbox(api_key=E2B_API_KEY, timeout=600)
VALID_MODELS = [
"llama-3.3-70b-versatile", "distil-whisper-large-v3-en", "gemma2-9b-it", "llama-3.1-8b-instant",
"llama-guard-3-8b", "llama3-70b-8192", "llama3-8b-8192", "mixtral-8x7b-32768", "whisper-large-v3",
"whisper-large-v3-turbo", "deepseek-r1-distill-llama-70b-specdec", "deepseek-r1-distill-llama-70b",
"llama-3.3-specdec", "llama-3.2-1b-preview", "llama-3.2-3b-preview", "llama-3.2-11b-vision-preview",
"llama-3.2-90b-vision-preview", "llama3-70b-8192"
]
CODE_PATTERN = re.compile(r"python\s*([\s\S]*?)
")
CODE_REQUIRED_MARKER = "[CODE_REQUIRED]"
CANVAS_START_MARKER = "[CANVAS_START]"
CANVAS_END_MARKER = "[CANVAS_END]"
MAX_CONTEXT_CHARS_PER_PAGE = 2000
MAX_TOTAL_CONTEXT_CHARS = 7000
WHOOGLE_CACHE: Dict[str, str] = {}
http_session = requests.Session()
http_session.headers.update({"User-Agent": "PlinkoChatBot/1.0"})
def operate_requests(prompt) -> Generator[Dict[str, str], None, Tuple[str, List[str]]]:
"""
Regroupe toutes les opérations :
1. Recherche sur Whoogle.
2. Limitation à 7 liens.
3. Extraction du contenu de chaque page (en parallèle).
4. Retourne le contexte obtenu et la liste des sources.
"""
# Utilisation de la clé API Groq déjà définie globalement
global GROQ_API_KEY
groq_client = Groq(api_key=GROQ_API_KEY)
textdef whoogle_search(query): """ Effectue une recherche via Whoogle et retourne une liste de liens. """ base_url = WHOOGLE_INSTANCE_URL + "/search" params = {"q": query} headers = {"User-Agent": "Mozilla/5.0"} try: response = requests.get(base_url, params=params, headers=headers) response.raise_for_status() except requests.exceptions.RequestException as e: logger.error(f"Erreur lors de la requête Whoogle : {e}") return [] soup = BeautifulSoup(response.text, "html.parser") # Extraire uniquement les liens commençant par "http" links = [result["href"] for result in soup.find_all("a", href=True) if result["href"].startswith("http")] return links # Fonction de summarisation via Groq (anciennement utilisée pour résumer le texte) # Nous ne l'utilisons plus ici car nous voulons intégrer directement le contenu extrait. @retry(stop=stop_after_attempt(5), wait=wait_exponential(multiplier=1, min=4, max=60), retry=retry_if_exception_type(httpx.HTTPError)) def summarize_with_groq(query, text_content): try: response = groq_client.chat.completions.create( model="qwen-2.5-32b", messages=[ { "role": "system", "content": ( "Vous êtes un assistant expert en extraction d'informations. " "Votre tâche est de lire le texte fourni et d'extraire les informations les plus importantes " "qui répondent à la question de recherche suivante : '{}'. " "Créez une note concise résumant les points clés et les informations pertinentes. " "Soyez précis et factuel." ).format(query) }, { "role": "user", "content": text_content } ], temperature=0.2 ) return response.choices[0].message.content.strip() except httpx.HTTPError as e: if e.response.status_code == 429: logger.warning(f"Erreur 429 lors de l'appel à Groq (summarize_with_groq). Détails: {e}") raise e else: logger.error(f"Erreur HTTP dans summarize_with_groq: {e}") raise except Exception as e: logger.error(f"Erreur inattendue dans summarize_with_groq: {e}") return "Erreur lors de la synthèse avec Groq." def fetch_and_summarize(link, query): """ Récupère le contenu d'une page et l'extrait de manière plus approfondie. Pour chaque page, on tente d'extraire le texte contenu dans un <article> ou, à défaut, dans les balises <p>. On tronque ensuite le contenu à MAX_CONTEXT_CHARS_PER_PAGE caractères. """ try: r = requests.get(link, timeout=20) r.raise_for_status() soup_page = BeautifulSoup(r.text, 'html.parser') # Tentative d'extraction du contenu principal article = soup_page.find('article') if article: text_content = article.get_text(separator='\n', strip=True) else: paragraphs = soup_page.find_all('p') if paragraphs: text_content = "\n".join(p.get_text() for p in paragraphs) else: text_content = soup_page.get_text(separator='\n', strip=True) # Tronquer le contenu s'il est trop long if len(text_content) > MAX_CONTEXT_CHARS_PER_PAGE: text_content = text_content[:MAX_CONTEXT_CHARS_PER_PAGE] + "..." logger.info(f"{link} : OK - Extraction du contenu (limité à {MAX_CONTEXT_CHARS_PER_PAGE} caractères).") # On renvoie directement le contenu extrait (plutôt qu'un résumé généré) return f"Source: {link}\nContenu:\n{text_content}\n\n" except requests.exceptions.RequestException: logger.error(f"{link} : Échec de la récupération de la page.") return f"Source: {link}\nContenu:\nErreur de récupération de la page.\n\n" except Exception as e: logger.error(f"{link} : Erreur lors du traitement de la page : {e}") return f"Source: {link}\nContenu:\nErreur lors du traitement de la page: {e}\n\n" def fetch_pages_and_summarize(links, query): """ Traite plusieurs pages en parallèle et retourne le contexte concaténé. """ results = [] with ThreadPoolExecutor(max_workers=10) as executor: future_to_link = {executor.submit(fetch_and_summarize, link, query): link for link in links} for future in as_completed(future_to_link): result = future.result() results.append(result) return "".join(results) # --- Déroulement principal de la fonction operate_requests --- yield {"type": "web_search_status", "content": "start"} # 1. Recherche sur Whoogle all_links = whoogle_search(prompt) if not all_links: logger.info("Aucun résultat trouvé pour la recherche web.") yield {"type": "web_search_status", "content": "end"} yield "", [] # Retourne un contexte vide et aucune source return # 2. Limiter à 7 liens limited_links = all_links[:7] logger.info(f"{len(limited_links)} lien(s) utilisé(s) pour la recherche web.") # 3. Extraction du contenu en parallèle context = fetch_pages_and_summarize(limited_links, prompt) # Tronquer le contexte total si nécessaire if len(context) > MAX_TOTAL_CONTEXT_CHARS: context = context[:MAX_TOTAL_CONTEXT_CHARS] yield {"type": "web_search_status", "content": "end"} yield context, limited_links return
def extract_code_blocks(text: str) -> Tuple[Optional[str], Optional[str]]:
"""Extrait les blocs de code Python et gère le marqueur [CODE_REQUIRED]."""
if CODE_REQUIRED_MARKER in text:
text_no_marker = text.replace(CODE_REQUIRED_MARKER, "", 1).strip()
code_match = CODE_PATTERN.search(text_no_marker)
if code_match:
return code_match.group(1).strip(), None
else:
code_match = CODE_PATTERN.search(text)
if code_match:
return None, code_match.group(1).strip() # Code à afficher sans exécution
return None, None
def run_code_in_sandbox(code: str) -> str:
"""Exécute le code Python dans le sandbox e2b et retourne le résultat."""
start_time = time.time()
try:
logger.info("Début de l'exécution du code dans le sandbox e2b.")
execution = None
method_name = next((method for method in ["run_code_sync", "execute_sync", "execute_code", "execute", "run_code", "run"] if hasattr(sandbox, method)), None)
if method_name:
logger.info(f"Utilisation de la méthode sandbox.{method_name}")
execution = getattr(sandbox, method_name)(code)
else:
error_msg = "Aucune méthode d'exécution de code disponible dans le sandbox."
logger.error(error_msg)
return error_msg
logs = execution.logs
stdout_logs = logs.stdout if hasattr(logs, 'stdout') else []
stderr_logs = logs.stderr if hasattr(logs, 'stderr') else []
stdout = "\n".join(stdout_logs)
stderr = "\n".join(stderr_logs)
if stderr:
return f"Stdout:\n{stdout}\n\nStderr:\n{stderr}"
return stdout
except Exception as e:
duration = time.time() - start_time
error_msg = f"Erreur lors de l'exécution du code dans e2b après {duration:.2f} secondes: {str(e)}"
logger.exception(error_msg)
return f"Erreur exécution code: {str(e)}"
def extract_numeric_result(exec_result: str) -> Optional[str]:
"""Extrait la dernière ligne numérique du résultat d'exécution de code."""
if "Stdout:" in exec_result:
stdout_section = exec_result.split("Stdout:\n", 1)[1].split("\n\nStderr:")[0].strip()
else:
stdout_section = exec_result.strip()
lines = [l.strip() for l in stdout_section.splitlines() if l.strip()]
if not lines:
return None
last_line = lines[-1]
if re.search(r'\d', last_line):
return last_line
return None
@app.route('/models', methods=['GET'])
def get_models() -> Response:
"""Retourne la liste des modèles valides."""
return jsonify({"models": VALID_MODELS})
@app.route('/chat', methods=['POST'])
def chat_endpoint() -> Response:
"""
Point d'entrée principal pour le chat.
Gère le streaming SSE, l'exécution de code et les canvas.
Intègre la recherche web si activée (configurable via la requête).
"""
@stream_with_context
@retry(stop=stop_after_attempt(5),
wait=wait_exponential(multiplier=1, min=4, max=60),
retry=retry_if_exception_type(httpx.HTTPError))
def stream_response():
data = request.get_json()
if not data:
logger.warning("Requête chat invalide: corps de requête manquant.")
yield format_sse_event({"type": "error", "content": "Requête chat invalide : corps de requête manquant."})
return
textenable_web_search = data.get('enable_web_search', True) messages = data.get('messages', []) model: str = data.get('model', 'llama3-70b-8192').strip() execute_code: bool = data.get('execute_code', True) if not messages or not isinstance(messages, list) or not model or model not in VALID_MODELS: logger.warning("Requête chat invalide: messages=%s, model=%s", messages, model) yield format_sse_event({"type": "error", "content": "Requête chat invalide : messages ou modèle incorrects."}) return system_web_context = "" last_user_message = messages[-1]['content'] if messages else "" web_sources: List[str] = [] if enable_web_search and last_user_message: try: web_search_generator = operate_requests(last_user_message) while True: try: event = next(web_search_generator) if isinstance(event, dict) and event.get('type') == 'web_search_status': yield format_sse_event(event) elif isinstance(event, tuple) and len(event) == 2: system_web_context, web_sources = event break except StopIteration: system_web_context = "" web_sources = [] break except Exception as e_outer: logger.error(f"Erreur lors de la recherche web : {e_outer}") system_web_context = "" web_sources = "" else: system_web_context = "" # Construction du prompt système avec intégration du contexte web si présent base_prompt = ( "🎯 **Rôle : Expert en calcul numérique précis et assistant polyvalent avec canvas interactifs (texte et code).**\n" "**Mission :** Répondre aux questions, effectuer des calculs numériques exacts, et assister l'utilisateur avec des canvas éditables et interactifs pour le **texte** (avec formatage Markdown) et le **code**. " "Maintiens le contexte de conversation, en particulier autour du code, des canvas et des résultats précédents.\n\n" ) web_context_prompt_part = f"**CONTEXTE DE RECHERCHE WEB :**\n{system_web_context}\n\n" if system_web_context else "" instructions_prompt_part = ( "**Si la question DEMANDE un CALCUL NUMÉRIQUE :**\n" "1. Réponds d'abord avec une courte phrase d'introduction expliquant que tu vas utiliser un code Python pour résoudre le problème. **Sois clair sur le contexte du calcul, en te référant si nécessaire aux étapes précédentes de la conversation et aux données ou résultats déjà obtenus.**\n" "2. Sur la ligne suivante, ajoute TOUJOURS le marqueur `[CODE_REQUIRED]` pour indiquer qu'un code va suivre.\n" "3. Ensuite, fournis le **BLOC DE CODE PYTHON** minimal, lisible et correct pour effectuer UNIQUEMENT LE CALCUL demandé et afficher UNIQUEMENT LE RÉSULTAT NUMÉRIQUE via `print(resultat_numerique)`.\n" "**IMPORTANT :** Ne génère RIEN D'AUTRE que ces 3 éléments, et dans cet ordre précis. Arrête-toi **ICI**. Attends l'exécution du code.\n\n" "**[INSTRUCTION SUITE - APRES EXECUTION DU CODE]**\n" "Après avoir fourni le résultat du code, CONTINUE la conversation pour expliquer le résultat EN DETAILS, en explicitant clairement et précisément **LE Résultat du code Python** que tu viens d'utiliser pour le calcul. Il faut que l'explication du résultat réponde à la question de l'utilisateur.\n\n" "**Si la question DEMANDE la CRÉATION ou MODIFICATION de CODE dans un canvas :**\n" "1. Indique clairement que tu vas créer un **canvas de code** interactif pour cela. Précise que l'utilisateur pourra te demander des modifications dans un 'chat de canvas' dédié qui va s'ouvrir.\n" "2. Encadre le contenu initial du canvas avec les marqueurs `[CANVAS_START]` et `[CANVAS_END]`.\n" " Exemple : 'Très bien, un canvas de code interactif est lancé pour un quick sort en C. Vous pouvez maintenant me demander des modifications directement dans le chat de canvas qui vient de s'ouvrir :\n" "[CANVAS_START]\n```c\n// Code Quick Sort en C\n...\n```\n[CANVAS_END]'\n" "3. **IMPORTANT :** Ne fournis PAS de code markdown ```python ... ``` après le canvas, sauf si cela est explicitement demandé et pertinent.\n\n" "**Si la question DEMANDE la CRÉATION ou MODIFICATION de TEXTE RICHE (avec markdown) dans un canvas :**\n" "1. Indique clairement que tu vas créer un **canvas de texte** interactif pour cela. Précise que l'utilisateur pourra te demander des modifications dans un 'chat de canvas' dédié.\n" "2. Encadre le contenu initial du canvas de texte avec les marqueurs `[CANVAS_START]` et `[CANVAS_END]`.\n" " Exemple : 'Très bien, un canvas de texte interactif est lancé. Le texte supporte le **gras**, l'*italique*, les listes, etc. Vous pouvez maintenant me demander des modifications directement dans le chat de canvas qui vient de s'ouvrir :\n" "[CANVAS_START]\n# Titre principal en gras\n\n* Item 1\n* Item 2\n\n[CANVAS_END]'\n" "3. **IMPORTANT :** Ne fournis PAS de code markdown ```python ... ``` après le canvas de texte, sauf si cela est explicitement demandé.\n\n" "**Si l'utilisateur pose une question dans le 'chat de canvas' :**\n" " - Interprète sa demande comme une instruction pour MODIFIER le contenu du canvas actuel.\n" " - Fournis le NOUVEAU contenu du canvas ENTIÈREMENT MIS À JOUR encadré par `[CANVAS_START]` et `[CANVAS_END]`.\n" " - Ajoute une courte phrase de confirmation indiquant que le canvas a été mis à jour.\n\n" "**Si la question NE DEMANDE PAS de CALCUL NUMÉRIQUE NI DE CANVAS :**\n" "Réponds directement et clairement à la question en tenant compte du contexte de la conversation.\n" "Si tu dois donner un exemple de code, utilise un bloc markdown ```python ... ``` MAIS SANS JAMAIS ajouter `[CODE_REQUIRED]` ni de canvas." ) system_prompt_content = base_prompt + web_context_prompt_part + instructions_prompt_part system_prompt = {"role": "system", "content": system_prompt_content} messages_with_system_prompt = [system_prompt] + messages full_reply_text = "" code_to_execute = None code_to_display = None is_code_used = False canvas_content = None code_executed = False prompt_tokens = 0 completion_tokens = 0 total_tokens = 0 initial_response_completed = False yield format_sse_event({"type": "analysis_status", "content": "start"}) try: response_stream = groq_client.chat.completions.create( model=model, messages=messages_with_system_prompt, temperature=0.0, stream=True, ) for chunk in response_stream: if initial_response_completed: break delta_content = chunk.choices[0].delta.content or "" delta_content_no_marker = delta_content.replace(CODE_REQUIRED_MARKER, "") full_reply_text += delta_content_no_marker # Vérifier et extraire le canvas s'il est présent if CANVAS_START_MARKER in full_reply_text and CANVAS_END_MARKER in full_reply_text: start_index = full_reply_text.find(CANVAS_START_MARKER) + len(CANVAS_START_MARKER) end_index = full_reply_text.find(CANVAS_END_MARKER) canvas_content_extracted = full_reply_text[start_index:end_index].strip() canvas_content = canvas_content_extracted delta_content_for_chat = delta_content_no_marker.replace(CANVAS_START_MARKER, "").replace(CANVAS_END_MARKER, "") yield format_sse_event({"type": "text", "content": delta_content_for_chat}) yield format_sse_event({"type": "canvas_start", "content": canvas_content}) continue else: yield format_sse_event({"type": "text", "content": delta_content_no_marker}) code_to_execute, code_to_display = extract_code_blocks(full_reply_text) if code_to_execute: initial_response_completed = True if execute_code and code_to_execute: is_code_used = True logger.info("Envoi événement CODE_BLOCK") yield format_sse_event({"type": "code_block", "content": code_to_execute}) yield format_sse_event({"type": "code_execution_start"}) execution_result = run_code_in_sandbox(code_to_execute) numeric_result = extract_numeric_result(execution_result) if numeric_result: code_executed = True logger.info("Envoi événement EXECUTED_CODE_RESULT") yield format_sse_event({"type": "executed_code_result", "content": numeric_result}) conclusion_prompt_content = ( f"**[INSTRUCTION SUITE - APRES EXECUTION DU CODE]** Tu as exécuté le code Python suivant:\n" f"```python\n{code_to_execute}\n```\n" f"Le résultat du calcul est: **{numeric_result}**. " "Maintenant, continue pour une conversation logique en te basant sur la question initiale." ) conclusion_prompt = [{"role": "user", "content": conclusion_prompt_content}] conclusion_stream = groq_client.chat.completions.create( model=model, messages=conclusion_prompt, temperature=0.0, stream=True, ) for chunk in conclusion_stream: delta_content = chunk.choices[0].delta.content or "" yield format_sse_event({"type": "text", "content": delta_content}) yield format_sse_event({"type": "final"}) else: code_executed = True logger.info("Envoi événement EXECUTED_CODE_ERROR") yield format_sse_event({"type": "executed_code_error", "content": execution_result}) yield format_sse_event({"type": "final", "result": "Fin de la réponse (erreur ou pas de résultat numérique)."}) if web_sources: # Envoyer les sources web si présentes yield format_sse_event({"type": "web_sources", "content": web_sources}) yield format_sse_event({"type": "sources_start"}) for source_url in web_sources: yield format_sse_event({"type": "source_item", "content": source_url}) yield format_sse_event({"type": "sources_end"}) prompt_tokens = 0 # Comptage non implémenté completion_tokens = 0 total_tokens = prompt_tokens + completion_tokens if not code_executed: logger.info("Envoi événement FINAL (sans code exécuté)") yield format_sse_event({"type": "final", "prompt_tokens": prompt_tokens, "completion_tokens": completion_tokens, "total_tokens": total_tokens}) else: yield format_sse_event({"type": "final", "prompt_tokens": prompt_tokens, "completion_tokens": completion_tokens, "total_tokens": total_tokens}) except httpx.HTTPError as e: if e.response.status_code == 429: error_message = f"Erreur 429 (Too Many Requests) dans stream_response. Détails: {e}" logger.warning(error_message) yield format_sse_event({"type": "error", "content": error_message}) raise e else: error_message = f"Erreur HTTP (non-429) dans stream_response. Détails: {e}" logger.exception(error_message) yield format_sse_event({"type": "error", "content": error_message}) except Exception as e: error_message = f"Erreur serveur IA: {str(e)}" logger.exception(error_message) yield format_sse_event({"type": "error", "content": error_message}) finally: yield format_sse_event({"type": "analysis_status", "content": "end"}) yield format_sse_event({"type": "done", "is_code_execution": is_code_used}) return Response(stream_with_context(stream_response()), mimetype='text/event-stream')
@app.route('/canvas_modify', methods=['POST'])
def canvas_modify_endpoint() -> Response:
data = request.get_json()
if not data:
logger.warning("Requête canvas_modify sans données.")
return jsonify({"error": "Requête canvas_modify invalide : corps de requête manquant."}), 400
user_message = data.get('message')
canvas_content = data.get('canvas_content')
model = data.get('model', 'llama3-70b-8192').strip()
if not user_message or canvas_content is None:
logger.warning("Requête canvas_modify incomplète: message=%s, canvas_content=%s", user_message, canvas_content)
return jsonify({"error": "Requête canvas_modify invalide : message ou contenu canvas manquant."}), 400
canvas_chat_messages = [
{"role": "system", "content": (
"Tu es dans un chat de canvas. L'utilisateur va te demander de modifier le contenu du canvas suivant. "
"Fournis UNIQUEMENT le NOUVEAU contenu du canvas ENTIÈREMENT MIS À JOUR, encadré par [CANVAS_START] et [CANVAS_END]. "
"Ne réponds pas avec du texte en dehors de ces marqueurs, sauf une courte phrase de confirmation après le canvas. "
"Maintiens le contexte des modifications précédentes du canvas, en te basant sur l'historique de ce chat canvas."
)},
{"role": "assistant", "content": f"[CANVAS_START]\n{canvas_content}\n[CANVAS_END] Voici le canvas actuel. Dites-moi comment le modifier."},
{"role": "user", "content": user_message}
]
text@retry(stop=stop_after_attempt(5), wait=wait_exponential(multiplier=1, min=4, max=60), retry=retry_if_exception_type(httpx.HTTPError)) def canvas_modify_groq_call(canvas_chat_messages, model): return groq_client.chat.completions.create( model=model, messages=canvas_chat_messages, temperature=0.0, stream=False, ) try: response_stream = canvas_modify_groq_call(canvas_chat_messages, model) ai_response_text = response_stream.choices[0].message.content or "" if CANVAS_START_MARKER in ai_response_text and CANVAS_END_MARKER in ai_response_text: start_index = ai_response_text.find(CANVAS_START_MARKER) + len(CANVAS_START_MARKER) end_index = ai_response_text.find(CANVAS_END_MARKER) updated_canvas_content = ai_response_text[start_index:end_index].strip() response_message = "Canvas mis à jour." logger.info("Canvas modifié avec succès.") return jsonify({"updated_canvas_content": updated_canvas_content, "response_message": response_message}) else: logger.error("Réponse IA canvas_modify invalide: marqueurs CANVAS_START/END manquants.") return jsonify({"error": "Réponse IA invalide pour la modification du canvas", "full_response": ai_response_text}), 500 except httpx.HTTPError as e: if e.response.status_code == 429: error_message = f"Erreur 429 (Too Many Requests) dans canvas_modify_endpoint. Détails: {e}" else: error_message = f"Erreur HTTP (non-429) dans canvas_modify_endpoint. Détails: {e}" logger.exception(error_message) return jsonify({"error": "Erreur lors de la modification du canvas après plusieurs tentatives.", "details": error_message}), 500 except Exception as e: error_message = f"Erreur serveur (canvas_modify): {str(e)}" logger.exception(error_message) return jsonify({"error": error_message}), 500
@app.route('/run', methods=['POST'])
def run_code_endpoint() -> Response:
"""
Point d'accès pour exécuter du code via l'API Piston (pour canvas code).
"""
data = request.get_json()
if not data:
logger.warning("Requête run sans données.")
return jsonify({"error": "Requête run invalide : corps de requête manquant."}), 400
language = data.get("language")
code = data.get("code")
stdin = data.get("stdin", "")
if not language or not code:
logger.warning("Requête run incomplète: language=%s, code=%s", language, code)
return jsonify({"error": "Requête run invalide : langage et code requis."}), 400
payload = {
"language": language,
"version": "*",
"files": [{"content": code}],
"stdin": stdin
}
try:
piston_response = http_session.post(PISTON_API_URL, json=payload, timeout=30)
piston_response.raise_for_status()
logger.info("Code exécuté avec Piston avec succès.")
return jsonify(piston_response.json())
except requests.RequestException as e:
error_message = f"Erreur lors de l'appel à l'API Piston: {str(e)}"
logger.exception(error_message)
piston_text = piston_response.text if 'piston_response' in locals() and piston_response is not None else ""
return jsonify({"error": "Erreur d'exécution du code via l'API Piston.", "piston_response": piston_text, "details": error_message}), 500
except Exception as e:
error_message = f"Erreur inattendue lors de l'exécution du code: {str(e)}"
logger.exception(error_message)
return jsonify({"error": "Erreur inattendue lors de l'exécution du code.", "details": error_message}), 500
@app.route('/run', methods=['OPTIONS'])
def run_code_options() -> Response:
"""Gère les requêtes OPTIONS pour l'endpoint /run (CORS preflight)."""
return jsonify({'message': 'OPTIONS requests allowed'}), 200
def format_sse_event(data: Dict[str, Any]) -> str:
"""Formate les données en événement SSE (Server-Sent Events)."""
return f"data: {json.dumps(data)}\n\n"
if name == 'main':
if os.environ.get('FLASK_ENV') == 'development':
app.run(debug=True, port=5000)
"""
Voici ci-dessous une version améliorée du code qui intègre plusieurs optimisations visant à améliorer la pertinence des recherches web, à mieux extraire le contexte de la requête de l’utilisateur et à fluidifier la conversation. Les améliorations portent notamment sur :
Mise en cache des recherches Whoogle
Pour éviter de refaire plusieurs fois la même recherche et améliorer la réactivité, nous utilisons un cache en mémoire.
Filtrage et validation des liens extraits
On s’assure de ne récupérer que des URL valides et de limiter la quantité d’informations récupérées par page.
Clarification des messages et instructions système
Les messages du prompt système ont été retravaillés pour être plus clairs et guider au mieux l’IA dans son rôle d’assistant expert.
Amélioration de la gestion des erreurs et des logs
Les messages de log sont enrichis afin de faciliter le débogage et fournir des retours plus explicites en cas d’erreur.
Vous trouverez ci-dessous le code refactorisé :
pythonfrom gevent import monkey monkey.patch_all() import os import re import json import logging from typing import Any, Dict, List, Optional, Tuple, Generator import time import requests from bs4 import BeautifulSoup from concurrent.futures import ThreadPoolExecutor, as_completed from flask import Flask, request, Response, jsonify, stream_with_context from flask_cors import CORS from groq import Groq from e2b_code_interpreter import Sandbox from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type import httpx # Configuration du logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) app = Flask(__name__) CORS(app) # Clés API (récupérées depuis les variables d'environnement) GROQ_API_KEY = os.environ.get("GROQ_API_KEY") E2B_API_KEY = os.environ.get("E2B_API_KEY") PISTON_API_URL = "https://emkc.org/api/v2/piston/execute" # API pour exécuter du code WHOOGLE_INSTANCE_URL = "https://chatapp-1-u8t2.onrender.com" # Instance Whoogle groq_client = Groq(api_key=GROQ_API_KEY) sandbox = Sandbox(api_key=E2B_API_KEY, timeout=600) # Liste des modèles acceptés VALID_MODELS = [ "llama-3.3-70b-versatile", "distil-whisper-large-v3-en", "gemma2-9b-it", "llama-3.1-8b-instant", "llama-guard-3-8b", "llama3-70b-8192", "llama3-8b-8192", "mixtral-8x7b-32768", "whisper-large-v3", "whisper-large-v3-turbo", "deepseek-r1-distill-llama-70b-specdec", "deepseek-r1-distill-llama-70b", "llama-3.3-specdec", "llama-3.2-1b-preview", "llama-3.2-3b-preview", "llama-3.2-11b-vision-preview", "llama-3.2-90b-vision-preview", "llama3-70b-8192" ] # Expressions régulières et marqueurs pour l'extraction de code et canvas CODE_PATTERN = re.compile(r"```python\s*([\s\S]*?)```", re.IGNORECASE) CODE_REQUIRED_MARKER = "[CODE_REQUIRED]" CANVAS_START_MARKER = "[CANVAS_START]" CANVAS_END_MARKER = "[CANVAS_END]" # Limites pour la quantité de texte extrait par page et au total MAX_CONTEXT_CHARS_PER_PAGE = 2000 MAX_TOTAL_CONTEXT_CHARS = 7000 # Cache pour les résultats Whoogle (clé : requête, valeur : résultat HTML ou liste de liens) WHOOGLE_CACHE: Dict[str, List[str]] = {} http_session = requests.Session() http_session.headers.update({"User-Agent": "PlinkoChatBot/1.0"}) def whoogle_search(query: str) -> List[str]: """ Effectue une recherche via Whoogle et retourne une liste de liens HTTP valides. Utilise un cache pour éviter des recherches redondantes. """ if query in WHOOGLE_CACHE: logger.info("Résultats en cache pour la requête Whoogle.") return WHOOGLE_CACHE[query] base_url = f"{WHOOGLE_INSTANCE_URL}/search" params = {"q": query} headers = {"User-Agent": "Mozilla/5.0"} try: response = requests.get(base_url, params=params, headers=headers, timeout=10) response.raise_for_status() except requests.exceptions.RequestException as e: logger.error(f"Erreur lors de la requête Whoogle : {e}") return [] soup = BeautifulSoup(response.text, "html.parser") # On récupère les liens commençant par "http" et on élimine les doublons links = list({result["href"] for result in soup.find_all("a", href=True) if result["href"].startswith("http")}) WHOOGLE_CACHE[query] = links logger.info(f"{len(links)} lien(s) trouvé(s) pour la requête : {query}") return links @retry(stop=stop_after_attempt(5), wait=wait_exponential(multiplier=1, min=4, max=60), retry=retry_if_exception_type(httpx.HTTPError)) def summarize_with_groq(query: str, text_content: str) -> str: """ (Ancienne fonction de synthèse, actuellement non utilisée.) """ try: response = groq_client.chat.completions.create( model="qwen-2.5-32b", messages=[ { "role": "system", "content": ( "Vous êtes un assistant expert en extraction d'informations. " "Votre tâche est de lire le texte fourni et d'extraire les informations les plus importantes " "pour répondre à la requête suivante : '{}'. " "Créez une note concise des points clés." ).format(query) }, {"role": "user", "content": text_content} ], temperature=0.2 ) return response.choices[0].message.content.strip() except httpx.HTTPError as e: logger.error(f"Erreur HTTP dans summarize_with_groq: {e}") raise except Exception as e: logger.error(f"Erreur inattendue dans summarize_with_groq: {e}") return "Erreur lors de la synthèse avec Groq." def fetch_and_extract(link: str, query: str) -> str: """ Récupère le contenu d'une page et en extrait le texte principal (balise <article> ou <p>). Tronque le contenu à MAX_CONTEXT_CHARS_PER_PAGE caractères. """ try: r = requests.get(link, timeout=20) r.raise_for_status() soup_page = BeautifulSoup(r.text, 'html.parser') article = soup_page.find('article') if article: text_content = article.get_text(separator='\n', strip=True) else: paragraphs = soup_page.find_all('p') text_content = "\n".join(p.get_text() for p in paragraphs) if paragraphs else soup_page.get_text(separator='\n', strip=True) if len(text_content) > MAX_CONTEXT_CHARS_PER_PAGE: text_content = text_content[:MAX_CONTEXT_CHARS_PER_PAGE] + "..." logger.info(f"Contenu extrait depuis {link} (limité à {MAX_CONTEXT_CHARS_PER_PAGE} caractères).") return f"Source: {link}\nContenu:\n{text_content}\n\n" except requests.exceptions.RequestException: logger.error(f"Échec de récupération de la page : {link}") return f"Source: {link}\nContenu:\nErreur de récupération de la page.\n\n" except Exception as e: logger.error(f"Erreur lors du traitement de la page {link} : {e}") return f"Source: {link}\nContenu:\nErreur lors du traitement de la page: {e}\n\n" def fetch_pages_and_extract(links: List[str], query: str) -> str: """ Traite plusieurs pages en parallèle et retourne le texte concaténé. """ results = [] with ThreadPoolExecutor(max_workers=10) as executor: future_to_link = {executor.submit(fetch_and_extract, link, query): link for link in links} for future in as_completed(future_to_link): results.append(future.result()) return "".join(results) def operate_requests(prompt: str) -> Generator[Any, None, Tuple[str, List[str]]]: """ Exécute l'ensemble des opérations de recherche web : 1. Recherche via Whoogle (avec mise en cache). 2. Limitation à 7 liens. 3. Extraction du contenu de chaque page en parallèle. 4. Retourne le contexte obtenu et la liste des sources. """ yield {"type": "web_search_status", "content": "start"} all_links = whoogle_search(prompt) if not all_links: logger.info("Aucun résultat trouvé pour la recherche web.") yield {"type": "web_search_status", "content": "end"} yield "", [] # Aucun contexte ni source return limited_links = all_links[:7] logger.info(f"{len(limited_links)} lien(s) utilisés pour la recherche web.") context = fetch_pages_and_extract(limited_links, prompt) if len(context) > MAX_TOTAL_CONTEXT_CHARS: context = context[:MAX_TOTAL_CONTEXT_CHARS] yield {"type": "web_search_status", "content": "end"} yield context, limited_links def extract_code_blocks(text: str) -> Tuple[Optional[str], Optional[str]]: """ Extrait les blocs de code Python selon le marqueur [CODE_REQUIRED]. Retourne (code_a_executer, code_a_afficher). """ if CODE_REQUIRED_MARKER in text: text_no_marker = text.replace(CODE_REQUIRED_MARKER, "", 1).strip() code_match = CODE_PATTERN.search(text_no_marker) if code_match: return code_match.group(1).strip(), None else: code_match = CODE_PATTERN.search(text) if code_match: return None, code_match.group(1).strip() return None, None def run_code_in_sandbox(code: str) -> str: """ Exécute le code Python dans le sandbox e2b et retourne le résultat. """ start_time = time.time() try: logger.info("Début de l'exécution du code dans le sandbox e2b.") method_name = next( (m for m in ["run_code_sync", "execute_sync", "execute_code", "execute", "run_code", "run"] if hasattr(sandbox, m)), None ) if not method_name: error_msg = "Aucune méthode d'exécution de code disponible dans le sandbox." logger.error(error_msg) return error_msg execution = getattr(sandbox, method_name)(code) stdout = "\n".join(getattr(execution.logs, 'stdout', [])) stderr = "\n".join(getattr(execution.logs, 'stderr', [])) if stderr: return f"Stdout:\n{stdout}\n\nStderr:\n{stderr}" return stdout except Exception as e: duration = time.time() - start_time error_msg = f"Erreur lors de l'exécution du code dans e2b après {duration:.2f} secondes: {str(e)}" logger.exception(error_msg) return f"Erreur exécution code: {str(e)}" def extract_numeric_result(exec_result: str) -> Optional[str]: """ Extrait la dernière ligne contenant une valeur numérique du résultat d'exécution. """ if "Stdout:" in exec_result: stdout_section = exec_result.split("Stdout:\n", 1)[1].split("\n\nStderr:")[0].strip() else: stdout_section = exec_result.strip() lines = [l.strip() for l in stdout_section.splitlines() if l.strip()] if lines and re.search(r'\d', lines[-1]): return lines[-1] return None def get_system_prompt(web_context: str) -> Dict[str, str]: """ Construit le prompt système en intégrant le contexte web (s'il existe) et des instructions précises pour l'IA. """ base_prompt = ( "🎯 Rôle : Vous êtes un expert en calcul numérique précis et un assistant polyvalent capable de gérer " "des canvas interactifs (texte et code).\n" "Mission : Répondre aux questions, effectuer des calculs numériques exacts, et assister l'utilisateur en " "maintenant le contexte de la conversation (notamment pour le code et les canvas).\n\n" ) web_context_part = f"CONTEXTE DE RECHERCHE WEB :\n{web_context}\n\n" if web_context else "" instructions = ( "Si la question demande un calcul numérique :\n" "1. Commencez par expliquer brièvement que vous allez utiliser du code Python pour résoudre le problème.\n" "2. Sur la ligne suivante, ajoutez le marqueur `[CODE_REQUIRED]` pour indiquer qu'un code va suivre.\n" "3. Fournissez ensuite un bloc de code Python minimal et lisible qui réalise uniquement le calcul demandé, " "et affiche le résultat numérique via `print(resultat_numerique)`.\n\n" "Après l'exécution du code, continuez en expliquant précisément le résultat obtenu.\n\n" "Si la question demande la création ou modification de code dans un canvas :\n" "1. Indiquez que vous allez créer un canvas de code interactif et précisez que l'utilisateur pourra demander " "des modifications ultérieures dans un chat dédié.\n" "2. Encadrez le contenu du canvas avec les marqueurs `[CANVAS_START]` et `[CANVAS_END]`.\n\n" "Si la question demande la création ou modification de texte riche (avec Markdown) dans un canvas :\n" "1. Indiquez que vous allez créer un canvas de texte interactif et mentionnez que l'utilisateur pourra demander " "des modifications par la suite.\n" "2. Encadrez le contenu initial du canvas avec `[CANVAS_START]` et `[CANVAS_END]`.\n\n" "Pour toute autre demande, répondez directement en tenant compte du contexte de la conversation." ) return {"role": "system", "content": base_prompt + web_context_part + instructions} def format_sse_event(data: Dict[str, Any]) -> str: """ Formate les données en événement SSE (Server-Sent Event). """ return f"data: {json.dumps(data)}\n\n" @app.route('/models', methods=['GET']) def get_models() -> Response: """Retourne la liste des modèles valides.""" return jsonify({"models": VALID_MODELS}) @app.route('/chat', methods=['POST']) def chat_endpoint() -> Response: """ Point d'entrée principal pour le chat. Gère le streaming SSE, l'exécution de code et la gestion des canvas. Intègre la recherche web (si activée) et construit le prompt système amélioré. """ @stream_with_context @retry(stop=stop_after_attempt(5), wait=wait_exponential(multiplier=1, min=4, max=60), retry=retry_if_exception_type(httpx.HTTPError)) def stream_response(): data = request.get_json() if not data: logger.warning("Requête chat invalide : corps de requête manquant.") yield format_sse_event({"type": "error", "content": "Requête chat invalide : corps de requête manquant."}) return enable_web_search = data.get('enable_web_search', True) messages = data.get('messages', []) model: str = data.get('model', 'llama3-70b-8192').strip() execute_code: bool = data.get('execute_code', True) if not messages or not isinstance(messages, list) or model not in VALID_MODELS: logger.warning("Requête chat invalide : messages ou modèle incorrects.") yield format_sse_event({"type": "error", "content": "Requête chat invalide : messages ou modèle incorrects."}) return # Récupérer le dernier message utilisateur pour la recherche web last_user_message = messages[-1]['content'] if messages else "" system_web_context = "" web_sources: List[str] = [] if enable_web_search and last_user_message: try: web_search_generator = operate_requests(last_user_message) for event in web_search_generator: if isinstance(event, dict) and event.get('type') == 'web_search_status': yield format_sse_event(event) elif isinstance(event, tuple) and len(event) == 2: system_web_context, web_sources = event break except Exception as e_outer: logger.error(f"Erreur lors de la recherche web : {e_outer}") system_web_context = "" web_sources = [] else: system_web_context = "" # Construction du prompt système amélioré system_prompt = get_system_prompt(system_web_context) messages_with_system = [system_prompt] + messages full_reply_text = "" code_to_execute = None code_to_display = None is_code_used = False code_executed = False yield format_sse_event({"type": "analysis_status", "content": "start"}) try: response_stream = groq_client.chat.completions.create( model=model, messages=messages_with_system, temperature=0.0, stream=True, ) initial_response_completed = False for chunk in response_stream: if initial_response_completed: break delta_content = chunk.choices[0].delta.content or "" # On retire le marqueur CODE_REQUIRED avant affichage delta_content_affiche = delta_content.replace(CODE_REQUIRED_MARKER, "") full_reply_text += delta_content_affiche # Vérifier la présence d'un canvas if CANVAS_START_MARKER in full_reply_text and CANVAS_END_MARKER in full_reply_text: start_index = full_reply_text.find(CANVAS_START_MARKER) + len(CANVAS_START_MARKER) end_index = full_reply_text.find(CANVAS_END_MARKER) canvas_content = full_reply_text[start_index:end_index].strip() # On envoie la partie texte hors canvas puis l'événement canvas delta_text = delta_content_affiche.replace(CANVAS_START_MARKER, "").replace(CANVAS_END_MARKER, "") yield format_sse_event({"type": "text", "content": delta_text}) yield format_sse_event({"type": "canvas_start", "content": canvas_content}) continue else: yield format_sse_event({"type": "text", "content": delta_content_affiche}) # Extraction des blocs de code code_to_execute, code_to_display = extract_code_blocks(full_reply_text) if code_to_execute: initial_response_completed = True # Si un bloc de code à exécuter a été détecté if execute_code and code_to_execute: is_code_used = True logger.info("Bloc de code à exécuter détecté.") yield format_sse_event({"type": "code_block", "content": code_to_execute}) yield format_sse_event({"type": "code_execution_start"}) execution_result = run_code_in_sandbox(code_to_execute) numeric_result = extract_numeric_result(execution_result) if numeric_result: code_executed = True yield format_sse_event({"type": "executed_code_result", "content": numeric_result}) # Envoi d'un prompt de conclusion pour expliquer le résultat du calcul conclusion_prompt_content = ( f"[INSTRUCTION SUITE - APRES EXECUTION DU CODE] Vous venez d'exécuter le code suivant :\n" f"```python\n{code_to_execute}\n```\n" f"Le résultat du calcul est : {numeric_result}\n" "Expliquez en détail ce résultat en lien avec la demande initiale." ) conclusion_prompt = [{"role": "user", "content": conclusion_prompt_content}] conclusion_stream = groq_client.chat.completions.create( model=model, messages=conclusion_prompt, temperature=0.0, stream=True, ) for chunk in conclusion_stream: delta = chunk.choices[0].delta.content or "" yield format_sse_event({"type": "text", "content": delta}) yield format_sse_event({"type": "final"}) else: code_executed = True yield format_sse_event({"type": "executed_code_error", "content": execution_result}) yield format_sse_event({"type": "final", "result": "Fin de la réponse (erreur ou pas de résultat numérique)."}) # Envoyer les sources web si elles existent if web_sources: yield format_sse_event({"type": "web_sources", "content": web_sources}) yield format_sse_event({"type": "sources_start"}) for source_url in web_sources: yield format_sse_event({"type": "source_item", "content": source_url}) yield format_sse_event({"type": "sources_end"}) # Envoi final des tokens (comptage simplifié ici) prompt_tokens = 0 completion_tokens = 0 total_tokens = prompt_tokens + completion_tokens yield format_sse_event({"type": "final", "prompt_tokens": prompt_tokens, "completion_tokens": completion_tokens, "total_tokens": total_tokens}) except httpx.HTTPError as e: if e.response.status_code == 429: error_message = f"Erreur 429 (Trop de requêtes) : {e}" logger.warning(error_message) yield format_sse_event({"type": "error", "content": error_message}) raise e else: error_message = f"Erreur HTTP : {e}" logger.exception(error_message) yield format_sse_event({"type": "error", "content": error_message}) except Exception as e: error_message = f"Erreur serveur IA : {str(e)}" logger.exception(error_message) yield format_sse_event({"type": "error", "content": error_message}) finally: yield format_sse_event({"type": "analysis_status", "content": "end"}) yield format_sse_event({"type": "done", "is_code_execution": is_code_used}) return Response(stream_with_context(stream_response()), mimetype='text/event-stream') @app.route('/canvas_modify', methods=['POST']) def canvas_modify_endpoint() -> Response: """ Endpoint pour modifier le contenu d'un canvas. Attend un message de l'utilisateur et le contenu actuel du canvas, puis demande à l'IA de proposer un nouveau contenu encadré par les marqueurs. """ data = request.get_json() if not data: logger.warning("Requête canvas_modify sans données.") return jsonify({"error": "Corps de requête manquant."}), 400 user_message = data.get('message') canvas_content = data.get('canvas_content') model = data.get('model', 'llama3-70b-8192').strip() if not user_message or canvas_content is None: logger.warning("Requête canvas_modify incomplète.") return jsonify({"error": "Message ou contenu canvas manquant."}), 400 canvas_chat_messages = [ {"role": "system", "content": ( "Vous êtes dans un chat dédié à la modification d'un canvas. " "L'utilisateur va vous demander de modifier le contenu suivant. " "Fournissez uniquement le nouveau contenu du canvas, intégralement encadré par " "[CANVAS_START] et [CANVAS_END]. Ajoutez ensuite une courte confirmation de la mise à jour." )}, {"role": "assistant", "content": f"[CANVAS_START]\n{canvas_content}\n[CANVAS_END] Canvas actuel."}, {"role": "user", "content": user_message} ] @retry(stop=stop_after_attempt(5), wait=wait_exponential(multiplier=1, min=4, max=60), retry=retry_if_exception_type(httpx.HTTPError)) def canvas_modify_groq_call(messages, model): return groq_client.chat.completions.create( model=model, messages=messages, temperature=0.0, stream=False, ) try: response_stream = canvas_modify_groq_call(canvas_chat_messages, model) ai_response_text = response_stream.choices[0].message.content or "" if CANVAS_START_MARKER in ai_response_text and CANVAS_END_MARKER in ai_response_text: start_index = ai_response_text.find(CANVAS_START_MARKER) + len(CANVAS_START_MARKER) end_index = ai_response_text.find(CANVAS_END_MARKER) updated_canvas_content = ai_response_text[start_index:end_index].strip() logger.info("Canvas modifié avec succès.") return jsonify({"updated_canvas_content": updated_canvas_content, "response_message": "Canvas mis à jour."}) else: logger.error("Réponse invalide pour la modification du canvas (marqueurs absents).") return jsonify({"error": "Réponse IA invalide pour la modification du canvas", "full_response": ai_response_text}), 500 except httpx.HTTPError as e: error_message = f"Erreur HTTP dans canvas_modify_endpoint : {e}" logger.exception(error_message) return jsonify({"error": "Erreur lors de la modification du canvas.", "details": error_message}), 500 except Exception as e: error_message = f"Erreur serveur (canvas_modify) : {str(e)}" logger.exception(error_message) return jsonify({"error": error_message}), 500 @app.route('/run', methods=['POST']) def run_code_endpoint() -> Response: """ Endpoint pour exécuter du code via l'API Piston (pour un canvas code). """ data = request.get_json() if not data: logger.warning("Requête run sans données.") return jsonify({"error": "Corps de requête manquant."}), 400 language = data.get("language") code = data.get("code") stdin = data.get("stdin", "") if not language or not code: logger.warning("Requête run incomplète : langage ou code manquant.") return jsonify({"error": "Langage et code requis."}), 400 payload = { "language": language, "version": "*", "files": [{"content": code}], "stdin": stdin } try: piston_response = http_session.post(PISTON_API_URL, json=payload, timeout=30) piston_response.raise_for_status() logger.info("Exécution du code via Piston réussie.") return jsonify(piston_response.json()) except requests.RequestException as e: error_message = f"Erreur lors de l'appel à l'API Piston : {str(e)}" logger.exception(error_message) piston_text = piston_response.text if 'piston_response' in locals() and piston_response is not None else "" return jsonify({"error": "Erreur d'exécution du code via l'API Piston.", "piston_response": piston_text, "details": error_message}), 500 except Exception as e: error_message = f"Erreur inattendue lors de l'exécution du code : {str(e)}" logger.exception(error_message) return jsonify({"error": "Erreur inattendue lors de l'exécution du code.", "details": error_message}), 500 @app.route('/run', methods=['OPTIONS']) def run_code_options() -> Response: """Gère les requêtes OPTIONS pour l'endpoint /run (pré-vol CORS).""" return jsonify({'message': 'OPTIONS requests allowed'}), 200 if __name__ == '__main__': if os.environ.get('FLASK_ENV') == 'development': app.run(debug=True, port=5000)
Recherche web améliorée et mise en cache :
La fonction whoogle_search
vérifie d’abord si la requête est présente dans le cache (dictionnaire WHOOGLE_CACHE
), ce qui permet de réduire les appels réseau et d’améliorer la rapidité de réponse.
Extraction de contenu robuste :
La fonction fetch_and_extract
tente d’extraire le contenu principal en se basant sur les balises <article>
ou <p>
et tronque le texte pour respecter une limite définie.
Instructions système plus claires :
Le prompt système est généré via la fonction get_system_prompt
afin d’assurer une cohérence dans les consignes fournies à l’IA, ce qui devrait améliorer la pertinence des réponses.
Gestion améliorée du streaming SSE et des erreurs :
Les fonctions de streaming et d’appel à l’API (notamment dans chat_endpoint
) fournissent désormais des messages plus explicites et détaillés, tout en s’assurant de la robustesse de l’exécution grâce à Tenacity.
N’hésitez pas à adapter davantage ces améliorations selon vos besoins ou à me demander des précisions supplémentaires.