Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -1,21 +1,270 @@
|
|
1 |
-
#
|
2 |
import streamlit as st
|
3 |
import asyncio
|
|
|
|
|
4 |
|
5 |
-
from
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
researcher_intro,
|
10 |
-
researcher_dev,
|
11 |
-
researcher_conclusion,
|
12 |
-
assembler,
|
13 |
-
create_plan_task,
|
14 |
-
compile_report_task,
|
15 |
-
llm,
|
16 |
-
)
|
17 |
|
18 |
from crewai import Agent, Crew, Process, Task
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
19 |
|
20 |
async def run_crew(topic):
|
21 |
"""Exécute le crew pour générer l'exposé."""
|
@@ -41,7 +290,7 @@ if st.button("Générer l'exposé"):
|
|
41 |
with st.spinner("Création de l'exposé en cours..."):
|
42 |
try:
|
43 |
# Exécuter le crew de manière asynchrone
|
44 |
-
result = asyncio.run(run_crew(topic))
|
45 |
st.success("Exposé généré avec succès!")
|
46 |
|
47 |
# Téléchargement du fichier PDF
|
|
|
1 |
+
# app.py
|
2 |
import streamlit as st
|
3 |
import asyncio
|
4 |
+
from typing import Any, List, Type
|
5 |
+
from pydantic import BaseModel, Field
|
6 |
|
7 |
+
from reportlab.lib.pagesizes import letter
|
8 |
+
from reportlab.platypus import SimpleDocTemplate, Paragraph
|
9 |
+
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
10 |
+
from reportlab.lib import colors
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
11 |
|
12 |
from crewai import Agent, Crew, Process, Task
|
13 |
+
from crewai_tools import SerperDevTool
|
14 |
+
from crewai import LLM
|
15 |
+
|
16 |
+
# --- PDF Tool from pdf_tool.py ---
|
17 |
+
class PDFToolInput(BaseModel):
|
18 |
+
content: str = Field(..., description="Contenu de l'exposé en format Markdown")
|
19 |
+
output_path: str = Field(..., description="Chemin de sortie du fichier PDF")
|
20 |
+
|
21 |
+
class PDFTool(BaseTool):
|
22 |
+
name: str = "Generate PDF"
|
23 |
+
description: str = "Outil pour générer un document PDF à partir de texte en format Markdown."
|
24 |
+
args_schema: Type[BaseModel] = PDFToolInput
|
25 |
+
|
26 |
+
def _run(
|
27 |
+
self,
|
28 |
+
content: str,
|
29 |
+
output_path: str = "expose.pdf",
|
30 |
+
**kwargs,
|
31 |
+
) -> str:
|
32 |
+
try:
|
33 |
+
doc = SimpleDocTemplate(output_path, pagesize=letter)
|
34 |
+
styles = getSampleStyleSheet()
|
35 |
+
|
36 |
+
# Ajout d'un style personnalisé pour le texte normal
|
37 |
+
styles.add(ParagraphStyle(name='CustomBodyText',
|
38 |
+
parent=styles['Normal'],
|
39 |
+
fontSize=12,
|
40 |
+
leading=14,
|
41 |
+
spaceAfter=10))
|
42 |
+
|
43 |
+
# Ajout d'un style personnalisé pour les titres de section
|
44 |
+
styles.add(ParagraphStyle(name='CustomHeading1',
|
45 |
+
parent=styles['Heading1'],
|
46 |
+
fontSize=18,
|
47 |
+
leading=22,
|
48 |
+
spaceBefore=20,
|
49 |
+
spaceAfter=6,
|
50 |
+
textColor=colors.HexColor("#2c3e50"))) # Couleur bleu foncé
|
51 |
+
|
52 |
+
# Ajout d'un style personnalisé pour les titres de sous-section
|
53 |
+
styles.add(ParagraphStyle(name='CustomHeading2',
|
54 |
+
parent=styles['Heading2'],
|
55 |
+
fontSize=14,
|
56 |
+
leading=18,
|
57 |
+
spaceBefore=10,
|
58 |
+
spaceAfter=4,
|
59 |
+
textColor=colors.HexColor("#34495e"))) # Couleur bleu-gris
|
60 |
+
|
61 |
+
# Séparer le contenu en sections en utilisant les sauts de ligne comme délimiteurs
|
62 |
+
sections = content.split("\n\n")
|
63 |
+
|
64 |
+
Story = []
|
65 |
+
for section in sections:
|
66 |
+
# Déterminer si la section est un titre de section, un titre de sous-section ou du texte normal
|
67 |
+
if section.startswith("# "):
|
68 |
+
# Titre de section
|
69 |
+
title = section[2:].strip()
|
70 |
+
Story.append(Paragraph(title, styles["CustomHeading1"]))
|
71 |
+
elif section.startswith("## "):
|
72 |
+
# Titre de sous-section
|
73 |
+
subtitle = section[3:].strip()
|
74 |
+
Story.append(Paragraph(subtitle, styles["CustomHeading2"]))
|
75 |
+
else:
|
76 |
+
# Texte normal
|
77 |
+
Story.append(Paragraph(section, styles["CustomBodyText"]))
|
78 |
+
|
79 |
+
doc.build(Story)
|
80 |
+
return f"Fichier PDF généré avec succès : {output_path}"
|
81 |
+
except Exception as e:
|
82 |
+
return f"Erreur lors de la génération du PDF : {e}"
|
83 |
+
|
84 |
+
|
85 |
+
# --- Crew utilities from crew_utils.py ---
|
86 |
+
|
87 |
+
# --- Définition des outils ---
|
88 |
+
search_tool = SerperDevTool()
|
89 |
+
pdf_tool = PDFTool()
|
90 |
+
|
91 |
+
# --- Définition du LLM (Gemini) ---
|
92 |
+
# Remplace par ta clé API
|
93 |
+
GEMINI_API_KEY = "AIzaSyD6yZxfVOnh63GXBJjakAupk9aP4CZrgrQ"
|
94 |
+
llm = LLM(
|
95 |
+
model="gemini/gemini-1.5-flash",
|
96 |
+
temperature=0.7,
|
97 |
+
timeout=120, # Seconds to wait for response
|
98 |
+
max_tokens=8000,
|
99 |
+
)
|
100 |
+
|
101 |
+
|
102 |
+
# --- Définition des agents ---
|
103 |
+
# Chef de Projet
|
104 |
+
project_manager = Agent(
|
105 |
+
role="Chef de Projet",
|
106 |
+
goal="Coordonner la création d'un exposé complet et de haute qualité sur le thème donné.",
|
107 |
+
backstory="Expert en gestion de projet et en coordination d'équipes, capable de diriger des projets complexes jusqu'à leur aboutissement.",
|
108 |
+
verbose=True,
|
109 |
+
llm=llm,
|
110 |
+
allow_delegation=True,
|
111 |
+
)
|
112 |
+
|
113 |
+
# Planificateur
|
114 |
+
planner = Agent(
|
115 |
+
role="Planificateur d'Exposé",
|
116 |
+
goal="Créer un plan détaillé et pertinent pour l'exposé.",
|
117 |
+
backstory="Spécialiste de la structuration de contenu, capable de générer des plans clairs et logiques pour des exposés complexes.",
|
118 |
+
verbose=True,
|
119 |
+
llm=llm,
|
120 |
+
)
|
121 |
+
|
122 |
+
# Rédacteurs (3 instances pour gérer l'introduction, le développement et la conclusion)
|
123 |
+
researcher_intro = Agent(
|
124 |
+
role="Rédacteur Spécialisé en Introduction",
|
125 |
+
goal="Rédiger une introduction captivante pour l'exposé.",
|
126 |
+
backstory="Expert en recherche et en rédaction, capable de produire du contenu informatif et bien écrit sur des sujets variés.",
|
127 |
+
verbose=True,
|
128 |
+
llm=llm,
|
129 |
+
tools=[search_tool],
|
130 |
+
)
|
131 |
+
|
132 |
+
researcher_dev = Agent(
|
133 |
+
role="Rédacteur Spécialisé en Développement",
|
134 |
+
goal="Rédiger les sections de développement de l'exposé.",
|
135 |
+
backstory="Expert en recherche et en rédaction, capable de produire du contenu informatif et bien écrit sur des sujets variés.",
|
136 |
+
verbose=True,
|
137 |
+
llm=llm,
|
138 |
+
tools=[search_tool],
|
139 |
+
)
|
140 |
+
|
141 |
+
researcher_conclusion = Agent(
|
142 |
+
role="Rédacteur Spécialisé en Conclusion",
|
143 |
+
goal="Rédiger une conclusion percutante pour l'exposé.",
|
144 |
+
backstory="Expert en recherche et en rédaction, capable de produire du contenu informatif et bien écrit sur des sujets variés.",
|
145 |
+
verbose=True,
|
146 |
+
llm=llm,
|
147 |
+
tools=[search_tool],
|
148 |
+
)
|
149 |
+
|
150 |
+
# Assembleur
|
151 |
+
assembler = Agent(
|
152 |
+
role="Assembleur d'Exposé",
|
153 |
+
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.",
|
154 |
+
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.",
|
155 |
+
verbose=True,
|
156 |
+
llm=llm,
|
157 |
+
tools=[pdf_tool],
|
158 |
+
)
|
159 |
+
|
160 |
+
# --- Définition des tâches ---
|
161 |
+
# Tâche pour le Planificateur
|
162 |
+
create_plan_task = Task(
|
163 |
+
description="""
|
164 |
+
Créez un plan détaillé pour un exposé sur le thème suivant : {topic}.
|
165 |
+
Le plan doit inclure une introduction, plusieurs sections principales (au moins 3), et une conclusion.
|
166 |
+
Assurez-vous que le plan est logique et couvre tous les aspects importants du sujet.
|
167 |
+
Divisez les sections principales en sous-sections si nécessaire pour une meilleure organisation.
|
168 |
+
""",
|
169 |
+
expected_output="Un plan d'exposé structuré avec des titres de sections et sous-sections, en format Markdown.",
|
170 |
+
agent=planner,
|
171 |
+
)
|
172 |
+
|
173 |
+
class SectionContent(BaseModel):
|
174 |
+
title: str = Field(..., description="Titre de la section")
|
175 |
+
content: str = Field(..., description="Contenu de la section")
|
176 |
+
|
177 |
+
# Tâches pour les Rédacteurs (exemple pour 3 sections, à adapter)
|
178 |
+
async def write_section_task(
|
179 |
+
researcher: Agent, section_title: str, context: List[Task]
|
180 |
+
) -> Task:
|
181 |
+
return Task(
|
182 |
+
description=f"""
|
183 |
+
Rédigez la section '{section_title}' de l'exposé en vous basant sur le plan fourni.
|
184 |
+
Effectuez des recherches approfondies en utilisant l'outil de recherche pour enrichir le contenu.
|
185 |
+
La section doit être informative, bien écrite et doit respecter le ton académique d'un exposé.
|
186 |
+
Voici le contexte: {context}
|
187 |
+
""",
|
188 |
+
expected_output=f"Texte complet et bien rédigé pour la section '{section_title}' de l'exposé, au format Markdown.",
|
189 |
+
agent=researcher,
|
190 |
+
tools=[search_tool],
|
191 |
+
output_pydantic=SectionContent, # Utilisez le modèle Pydantic ici
|
192 |
+
context=context,
|
193 |
+
)
|
194 |
+
|
195 |
+
# Tâche pour l'Assembleur
|
196 |
+
compile_report_task = Task(
|
197 |
+
description="""
|
198 |
+
Compilez toutes les sections rédigées de l'exposé dans un seul document.
|
199 |
+
Organisez les sections selon le plan fourni par le planificateur.
|
200 |
+
Générez un document PDF final prêt pour la présentation.
|
201 |
+
Assurez-vous que le document est bien structuré, facile à lire et qu'il respecte les conventions d'un exposé académique.
|
202 |
+
Voici le contexte: {context}
|
203 |
+
""",
|
204 |
+
expected_output="Un document PDF complet de l'exposé, prêt à être présenté.",
|
205 |
+
agent=assembler,
|
206 |
+
tools=[pdf_tool],
|
207 |
+
)
|
208 |
+
|
209 |
+
# --- Orchestration des tâches avec un processus hiérarchique ---
|
210 |
+
async def process_section_tasks(
|
211 |
+
manager: Agent,
|
212 |
+
plan: Any, # Remplace Any par le type de retour attendu de la tâche create_plan_task
|
213 |
+
researchers: List[Agent],
|
214 |
+
):
|
215 |
+
# Assume que plan est une liste de titres de sections
|
216 |
+
sections = plan.split("\n") # Adapter selon le format réel du plan
|
217 |
+
section_tasks: List[Task] = []
|
218 |
+
|
219 |
+
for i, section in enumerate(sections):
|
220 |
+
if section != "":
|
221 |
+
researcher = researchers[i % len(researchers)]
|
222 |
+
section_task = await write_section_task(
|
223 |
+
researcher=researcher,
|
224 |
+
section_title=section.strip(),
|
225 |
+
context=[create_plan_task],
|
226 |
+
)
|
227 |
+
section_tasks.append(section_task)
|
228 |
+
|
229 |
+
return section_tasks
|
230 |
+
|
231 |
+
class ExposeCrew(Crew):
|
232 |
+
def __init__(self, agents, tasks, process, manager_llm, verbose):
|
233 |
+
super().__init__(agents=agents, tasks=tasks, process=process, manager_llm=manager_llm, verbose=verbose)
|
234 |
+
|
235 |
+
async def kickoff(self, inputs: dict = {}) -> str:
|
236 |
+
# Exécuter la tâche de création du plan
|
237 |
+
plan = await create_plan_task.execute(context=inputs)
|
238 |
+
self.log.info(f"Plan de l'exposé : {plan}")
|
239 |
+
|
240 |
+
# Créer et exécuter les tâches de rédaction de section en parallèle
|
241 |
+
section_tasks = await process_section_tasks(
|
242 |
+
manager=self.manager_llm,
|
243 |
+
plan=plan,
|
244 |
+
researchers=[researcher_intro, researcher_dev, researcher_conclusion],
|
245 |
+
)
|
246 |
+
|
247 |
+
sections_results = await asyncio.gather(*[task.execute(context=[create_plan_task]) for task in section_tasks])
|
248 |
+
section_outputs = [result.content for result in sections_results]
|
249 |
+
|
250 |
+
self.log.info(f"Sections rédigées : {section_outputs}")
|
251 |
+
|
252 |
+
# Mettre à jour la description de la tâche de compilation avec les sections rédigées
|
253 |
+
compile_report_task.description = f"""
|
254 |
+
Compilez toutes les sections rédigées de l'exposé dans un seul document.
|
255 |
+
Organisez les sections selon le plan fourni par le planificateur :
|
256 |
+
{plan}
|
257 |
+
Sections rédigées :
|
258 |
+
{section_outputs}
|
259 |
+
Générez un document PDF final prêt pour la présentation.
|
260 |
+
"""
|
261 |
+
compile_report_task.context = section_tasks
|
262 |
+
|
263 |
+
# Exécuter la tâche de compilation
|
264 |
+
result = await compile_report_task.execute()
|
265 |
+
return result
|
266 |
+
|
267 |
+
# --- Streamlit App from app.py ---
|
268 |
|
269 |
async def run_crew(topic):
|
270 |
"""Exécute le crew pour générer l'exposé."""
|
|
|
290 |
with st.spinner("Création de l'exposé en cours..."):
|
291 |
try:
|
292 |
# Exécuter le crew de manière asynchrone
|
293 |
+
result = await asyncio.run(run_crew(topic))
|
294 |
st.success("Exposé généré avec succès!")
|
295 |
|
296 |
# Téléchargement du fichier PDF
|