Spaces:
Sleeping
Sleeping
# app.py | |
import streamlit as st | |
import asyncio | |
from typing import Any, List, Type | |
from pydantic import BaseModel, Field | |
from reportlab.lib.pagesizes import letter | |
from reportlab.platypus import SimpleDocTemplate, Paragraph | |
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle | |
from reportlab.lib import colors | |
from crewai import Agent, Crew, Process, Task | |
from crewai_tools import SerperDevTool | |
from crewai import LLM | |
# --- PDF Tool from pdf_tool.py --- | |
class PDFToolInput(BaseModel): | |
content: str = Field(..., description="Contenu de l'exposé en format Markdown") | |
output_path: str = Field(..., description="Chemin de sortie du fichier PDF") | |
class PDFTool(BaseTool): | |
name: str = "Generate PDF" | |
description: str = "Outil pour générer un document PDF à partir de texte en format Markdown." | |
args_schema: Type[BaseModel] = PDFToolInput | |
def _run( | |
self, | |
content: str, | |
output_path: str = "expose.pdf", | |
**kwargs, | |
) -> str: | |
try: | |
doc = SimpleDocTemplate(output_path, pagesize=letter) | |
styles = getSampleStyleSheet() | |
# Ajout d'un style personnalisé pour le texte normal | |
styles.add(ParagraphStyle(name='CustomBodyText', | |
parent=styles['Normal'], | |
fontSize=12, | |
leading=14, | |
spaceAfter=10)) | |
# Ajout d'un style personnalisé pour les titres de section | |
styles.add(ParagraphStyle(name='CustomHeading1', | |
parent=styles['Heading1'], | |
fontSize=18, | |
leading=22, | |
spaceBefore=20, | |
spaceAfter=6, | |
textColor=colors.HexColor("#2c3e50"))) # Couleur bleu foncé | |
# Ajout d'un style personnalisé pour les titres de sous-section | |
styles.add(ParagraphStyle(name='CustomHeading2', | |
parent=styles['Heading2'], | |
fontSize=14, | |
leading=18, | |
spaceBefore=10, | |
spaceAfter=4, | |
textColor=colors.HexColor("#34495e"))) # Couleur bleu-gris | |
# Séparer le contenu en sections en utilisant les sauts de ligne comme délimiteurs | |
sections = content.split("\n\n") | |
Story = [] | |
for section in sections: | |
# Déterminer si la section est un titre de section, un titre de sous-section ou du texte normal | |
if section.startswith("# "): | |
# Titre de section | |
title = section[2:].strip() | |
Story.append(Paragraph(title, styles["CustomHeading1"])) | |
elif section.startswith("## "): | |
# Titre de sous-section | |
subtitle = section[3:].strip() | |
Story.append(Paragraph(subtitle, styles["CustomHeading2"])) | |
else: | |
# Texte normal | |
Story.append(Paragraph(section, styles["CustomBodyText"])) | |
doc.build(Story) | |
return f"Fichier PDF généré avec succès : {output_path}" | |
except Exception as e: | |
return f"Erreur lors de la génération du PDF : {e}" | |
# --- Crew utilities from crew_utils.py --- | |
# --- Définition des outils --- | |
search_tool = SerperDevTool() | |
pdf_tool = PDFTool() | |
# --- Définition du LLM (Gemini) --- | |
# Remplace par ta clé API | |
GEMINI_API_KEY = "AIzaSyD6yZxfVOnh63GXBJjakAupk9aP4CZrgrQ" | |
llm = LLM( | |
model="gemini/gemini-1.5-flash", | |
temperature=0.7, | |
timeout=120, # Seconds to wait for response | |
max_tokens=8000, | |
) | |
# --- Définition des agents --- | |
# Chef de Projet | |
project_manager = Agent( | |
role="Chef de Projet", | |
goal="Coordonner la création d'un exposé complet et de haute qualité sur le thème donné.", | |
backstory="Expert en gestion de projet et en coordination d'équipes, capable de diriger des projets complexes jusqu'à leur aboutissement.", | |
verbose=True, | |
llm=llm, | |
allow_delegation=True, | |
) | |
# Planificateur | |
planner = Agent( | |
role="Planificateur d'Exposé", | |
goal="Créer un plan détaillé et pertinent pour l'exposé.", | |
backstory="Spécialiste de la structuration de contenu, capable de générer des plans clairs et logiques pour des exposés complexes.", | |
verbose=True, | |
llm=llm, | |
) | |
# Rédacteurs (3 instances pour gérer l'introduction, le développement et la conclusion) | |
researcher_intro = Agent( | |
role="Rédacteur Spécialisé en Introduction", | |
goal="Rédiger une introduction captivante pour l'exposé.", | |
backstory="Expert en recherche et en rédaction, capable de produire du contenu informatif et bien écrit sur des sujets variés.", | |
verbose=True, | |
llm=llm, | |
tools=[search_tool], | |
) | |
researcher_dev = Agent( | |
role="Rédacteur Spécialisé en Développement", | |
goal="Rédiger les sections de développement de l'exposé.", | |
backstory="Expert en recherche et en rédaction, capable de produire du contenu informatif et bien écrit sur des sujets variés.", | |
verbose=True, | |
llm=llm, | |
tools=[search_tool], | |
) | |
researcher_conclusion = Agent( | |
role="Rédacteur Spécialisé en Conclusion", | |
goal="Rédiger une conclusion percutante pour l'exposé.", | |
backstory="Expert en recherche et en rédaction, capable de produire du contenu informatif et bien écrit sur des sujets variés.", | |
verbose=True, | |
llm=llm, | |
tools=[search_tool], | |
) | |
# Assembleur | |
assembler = Agent( | |
role="Assembleur d'Exposé", | |
goal="Compiler toutes les sections rédigées de l'exposé dans un seul document et générer un document PDF final de qualité professionnelle.", | |
backstory="Expert en mise en forme et en compilation de documents, capable de transformer des contenus distincts en un document final harmonieux et esthétique.", | |
verbose=True, | |
llm=llm, | |
tools=[pdf_tool], | |
) | |
# --- Définition des tâches --- | |
# Tâche pour le Planificateur | |
create_plan_task = Task( | |
description=""" | |
Créez un plan détaillé pour un exposé sur le thème suivant : {topic}. | |
Le plan doit inclure une introduction, plusieurs sections principales (au moins 3), et une conclusion. | |
Assurez-vous que le plan est logique et couvre tous les aspects importants du sujet. | |
Divisez les sections principales en sous-sections si nécessaire pour une meilleure organisation. | |
""", | |
expected_output="Un plan d'exposé structuré avec des titres de sections et sous-sections, en format Markdown.", | |
agent=planner, | |
) | |
class SectionContent(BaseModel): | |
title: str = Field(..., description="Titre de la section") | |
content: str = Field(..., description="Contenu de la section") | |
# Tâches pour les Rédacteurs (exemple pour 3 sections, à adapter) | |
async def write_section_task( | |
researcher: Agent, section_title: str, context: List[Task] | |
) -> Task: | |
return Task( | |
description=f""" | |
Rédigez la section '{section_title}' de l'exposé en vous basant sur le plan fourni. | |
Effectuez des recherches approfondies en utilisant l'outil de recherche pour enrichir le contenu. | |
La section doit être informative, bien écrite et doit respecter le ton académique d'un exposé. | |
Voici le contexte: {context} | |
""", | |
expected_output=f"Texte complet et bien rédigé pour la section '{section_title}' de l'exposé, au format Markdown.", | |
agent=researcher, | |
tools=[search_tool], | |
output_pydantic=SectionContent, # Utilisez le modèle Pydantic ici | |
context=context, | |
) | |
# Tâche pour l'Assembleur | |
compile_report_task = Task( | |
description=""" | |
Compilez toutes les sections rédigées de l'exposé dans un seul document. | |
Organisez les sections selon le plan fourni par le planificateur. | |
Générez un document PDF final prêt pour la présentation. | |
Assurez-vous que le document est bien structuré, facile à lire et qu'il respecte les conventions d'un exposé académique. | |
Voici le contexte: {context} | |
""", | |
expected_output="Un document PDF complet de l'exposé, prêt à être présenté.", | |
agent=assembler, | |
tools=[pdf_tool], | |
) | |
# --- Orchestration des tâches avec un processus hiérarchique --- | |
async def process_section_tasks( | |
manager: Agent, | |
plan: Any, # Remplace Any par le type de retour attendu de la tâche create_plan_task | |
researchers: List[Agent], | |
): | |
# Assume que plan est une liste de titres de sections | |
sections = plan.split("\n") # Adapter selon le format réel du plan | |
section_tasks: List[Task] = [] | |
for i, section in enumerate(sections): | |
if section != "": | |
researcher = researchers[i % len(researchers)] | |
section_task = await write_section_task( | |
researcher=researcher, | |
section_title=section.strip(), | |
context=[create_plan_task], | |
) | |
section_tasks.append(section_task) | |
return section_tasks | |
class ExposeCrew(Crew): | |
def __init__(self, agents, tasks, process, manager_llm, verbose): | |
super().__init__(agents=agents, tasks=tasks, process=process, manager_llm=manager_llm, verbose=verbose) | |
async def kickoff(self, inputs: dict = {}) -> str: | |
# Exécuter la tâche de création du plan | |
plan = await create_plan_task.execute(context=inputs) | |
self.log.info(f"Plan de l'exposé : {plan}") | |
# Créer et exécuter les tâches de rédaction de section en parallèle | |
section_tasks = await process_section_tasks( | |
manager=self.manager_llm, | |
plan=plan, | |
researchers=[researcher_intro, researcher_dev, researcher_conclusion], | |
) | |
sections_results = await asyncio.gather(*[task.execute(context=[create_plan_task]) for task in section_tasks]) | |
section_outputs = [result.content for result in sections_results] | |
self.log.info(f"Sections rédigées : {section_outputs}") | |
# Mettre à jour la description de la tâche de compilation avec les sections rédigées | |
compile_report_task.description = f""" | |
Compilez toutes les sections rédigées de l'exposé dans un seul document. | |
Organisez les sections selon le plan fourni par le planificateur : | |
{plan} | |
Sections rédigées : | |
{section_outputs} | |
Générez un document PDF final prêt pour la présentation. | |
""" | |
compile_report_task.context = section_tasks | |
# Exécuter la tâche de compilation | |
result = await compile_report_task.execute() | |
return result | |
# --- Streamlit App from app.py --- | |
async def run_crew(topic): | |
"""Exécute le crew pour générer l'exposé.""" | |
crew = ExposeCrew( | |
agents=[project_manager, planner, researcher_intro, researcher_dev, researcher_conclusion, assembler], | |
tasks=[create_plan_task, compile_report_task], | |
process=Process.hierarchical, | |
manager_llm=llm, | |
verbose=True, | |
) | |
result = await crew.kickoff(inputs={"topic": topic}) | |
return result | |
# Interface Streamlit | |
st.title("Générateur d'Exposés avec CrewAI") | |
topic = st.text_input("Entrez le thème de l'exposé :") | |
if st.button("Générer l'exposé"): | |
if not topic: | |
st.error("Veuillez entrer un thème pour l'exposé.") | |
else: | |
with st.spinner("Création de l'exposé en cours..."): | |
try: | |
# Exécuter le crew de manière asynchrone | |
result = await asyncio.run(run_crew(topic)) | |
st.success("Exposé généré avec succès!") | |
# Téléchargement du fichier PDF | |
with open("expose.pdf", "rb") as file: | |
st.download_button( | |
label="Télécharger l'exposé en PDF", | |
data=file, | |
file_name="expose.pdf", | |
mime="application/pdf" | |
) | |
except Exception as e: | |
st.error(f"Une erreur s'est produite : {e}") |