|
|
from langchain_groq import ChatGroq |
|
|
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage |
|
|
from langgraph.graph import StateGraph, END |
|
|
from typing import TypedDict, Sequence, Annotated, Union |
|
|
from langchain_core.messages import BaseMessage |
|
|
from dotenv import load_dotenv |
|
|
from langchain_core.tools import tool |
|
|
import os |
|
|
from langgraph.graph.message import add_messages |
|
|
from langgraph.prebuilt import ToolNode |
|
|
import asyncio |
|
|
|
|
|
from src.agents.researcher_agent import ResearcherAgent |
|
|
from src.agents.content_extractor_agent import ContentExtractorAgent |
|
|
from src.agents.summarizer_agent import SummarizerAgent |
|
|
from src.agents.global_synthesizer_agent import GlobalSynthesizerAgent |
|
|
from src.models.research_models import ResearchQuery |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
researcher_agent = ResearcherAgent() |
|
|
content_extractor_agent = ContentExtractorAgent() |
|
|
summarizer_agent = SummarizerAgent() |
|
|
global_synthesizer_agent = GlobalSynthesizerAgent() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@tool |
|
|
def research_complete_pipeline(topic: str, max_results: Union[int, str] = 2) -> str: |
|
|
"""Exécute un pipeline de recherche complet sur un sujet donné. |
|
|
|
|
|
Ce tool encapsule 4 agents qui travaillent ensemble : |
|
|
1. ResearcherAgent : recherche web et extraction de mots-clés |
|
|
2. ContentExtractorAgent : extraction du contenu des pages |
|
|
3. SummarizerAgent : création de résumés détaillés |
|
|
4. GlobalSynthesizerAgent : synthèse globale finale |
|
|
|
|
|
Args: |
|
|
topic: Le sujet de recherche (ex: "impact de l'IA sur l'emploi") |
|
|
max_results: Nombre de sources à analyser (2-10, défaut: 2) |
|
|
|
|
|
Returns: |
|
|
Un rapport complet au format texte avec résumé exécutif et analyse détaillée |
|
|
""" |
|
|
|
|
|
if isinstance(max_results, str): |
|
|
try: |
|
|
max_results = int(max_results) |
|
|
except ValueError: |
|
|
max_results = 2 |
|
|
max_results = max(2, min(max_results, 10)) |
|
|
|
|
|
async def run_pipeline(): |
|
|
print(f"\n{'='*60}") |
|
|
print(f"🚀 DÉMARRAGE DU PIPELINE DE RECHERCHE") |
|
|
print(f"📋 Sujet: {topic}") |
|
|
print(f"📊 Sources à analyser: {max_results}") |
|
|
print(f"{'='*60}\n") |
|
|
|
|
|
|
|
|
print("🔍 [1/4] Recherche web en cours...") |
|
|
query = ResearchQuery( |
|
|
topic=topic, |
|
|
keywords=await researcher_agent.extract_keywords_with_llm(topic), |
|
|
max_results=max_results, |
|
|
search_depth="basic" |
|
|
) |
|
|
research_data = await researcher_agent.process(query) |
|
|
print(f"✅ Trouvé {research_data.total_found} sources") |
|
|
|
|
|
|
|
|
print("\n📄 [2/4] Extraction du contenu...") |
|
|
extraction_data = await content_extractor_agent.process_from_research_output( |
|
|
research_output=research_data |
|
|
) |
|
|
print(f"✅ Extrait {extraction_data.successful_extractions} documents") |
|
|
|
|
|
|
|
|
print("\n📝 [3/4] Création des résumés...") |
|
|
summarization_data = await summarizer_agent.process_from_extraction_result( |
|
|
extraction_result=extraction_data |
|
|
) |
|
|
print(f"✅ Généré {summarization_data.total_documents} résumés") |
|
|
|
|
|
|
|
|
print("\n🎯 [4/4] Synthèse globale...") |
|
|
global_synthesis = await global_synthesizer_agent.process_from_summarization_output( |
|
|
summarization_output=summarization_data |
|
|
) |
|
|
print(f"✅ Rapport final généré ({global_synthesis.final_report.word_count} mots)") |
|
|
|
|
|
print(f"\n{'='*60}") |
|
|
print("✨ PIPELINE TERMINÉ AVEC SUCCÈS") |
|
|
print(f"{'='*60}\n") |
|
|
|
|
|
|
|
|
return global_synthesis.formatted_outputs.get('markdown', |
|
|
global_synthesis.formatted_outputs.get('text', |
|
|
str(global_synthesis)) |
|
|
) |
|
|
|
|
|
return asyncio.run(run_pipeline()) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AgentState(TypedDict): |
|
|
messages: Annotated[Sequence[BaseMessage], add_messages] |
|
|
|
|
|
|
|
|
load_dotenv() |
|
|
api_key = os.getenv("GROQ_API_KEY") |
|
|
if not api_key: |
|
|
raise ValueError("GROQ_API_KEY non définie dans .env") |
|
|
|
|
|
|
|
|
tools = [research_complete_pipeline] |
|
|
model = ChatGroq( |
|
|
model="llama-3.1-8b-instant", |
|
|
temperature=0.3, |
|
|
max_tokens=2048*2, |
|
|
api_key=api_key |
|
|
).bind_tools(tools) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def model_call(state: AgentState) -> AgentState: |
|
|
"""Nœud qui appelle le LLM pour décider quoi faire""" |
|
|
|
|
|
system_prompt = SystemMessage(content="""Tu es un assistant de recherche intelligent. |
|
|
|
|
|
🎯 TON RÔLE: |
|
|
Tu aides les utilisateurs à obtenir des résumés et analyses sur n'importe quel sujet. |
|
|
|
|
|
🔧 TON OUTIL: |
|
|
Tu as accès à un outil puissant appelé 'research_complete_pipeline' qui : |
|
|
- Effectue des recherches web automatiques |
|
|
- Extrait et analyse le contenu |
|
|
- Génère des résumés détaillés |
|
|
- Produit une synthèse globale complète |
|
|
|
|
|
📋 QUAND L'UTILISER: |
|
|
Utilise cet outil quand l'utilisateur demande : |
|
|
- Un résumé sur un sujet |
|
|
- Des informations sur un topic |
|
|
- Une analyse d'un domaine |
|
|
- Une recherche documentée |
|
|
|
|
|
💡 COMMENT L'UTILISER: |
|
|
- Identifie le sujet principal de la demande |
|
|
- Appelle research_complete_pipeline avec le sujet en français clair |
|
|
- Utilise max_results=2 pour une recherche standard |
|
|
|
|
|
✅ EXEMPLES: |
|
|
User: "Résume l'impact de l'IA sur l'emploi" |
|
|
→ Appelle: research_complete_pipeline(topic="impact de l'intelligence artificielle sur le marché de l'emploi", max_results=2) |
|
|
User: "Fais-moi une analyse complète sur le changement climatique" |
|
|
→ Appelle: research_complete_pipeline(topic="changement climatique", max_results=3) |
|
|
|
|
|
⚠️ IMPORTANT: |
|
|
- N'essaie PAS de faire la recherche toi-même |
|
|
- Utilise TOUJOURS l'outil pour les demandes de recherche |
|
|
- Le résultat de l'outil est déjà un rapport complet formaté |
|
|
- Tu peux présenter le résultat directement à l'utilisateur |
|
|
""" |
|
|
|
|
|
) |
|
|
|
|
|
messages = state["messages"] |
|
|
response = model.invoke([system_prompt] + messages) |
|
|
return {"messages": [response]} |
|
|
|
|
|
def should_continue(state: AgentState) -> str: |
|
|
"""Décide si on continue avec des outils ou si on termine""" |
|
|
messages = state["messages"] |
|
|
last_message = messages[-1] |
|
|
|
|
|
|
|
|
if hasattr(last_message, 'tool_calls') and last_message.tool_calls: |
|
|
return "continue" |
|
|
else: |
|
|
return "end" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
graph = StateGraph(AgentState) |
|
|
|
|
|
|
|
|
graph.add_node("llm", model_call) |
|
|
tool_node = ToolNode(tools=tools) |
|
|
graph.add_node("tools", tool_node) |
|
|
|
|
|
|
|
|
graph.set_entry_point("llm") |
|
|
|
|
|
|
|
|
graph.add_conditional_edges( |
|
|
"llm", |
|
|
should_continue, |
|
|
{ |
|
|
"continue": "tools", |
|
|
"end": END, |
|
|
}, |
|
|
) |
|
|
|
|
|
|
|
|
graph.add_edge("tools", "llm") |
|
|
|
|
|
|
|
|
app = graph.compile() |