ysharma HF staff commited on
Commit
71882f2
Β·
verified Β·
1 Parent(s): 0d16332

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +214 -209
app.py CHANGED
@@ -1,7 +1,7 @@
1
  from crewai import Agent, Task, Crew
2
  import gradio as gr
3
  import asyncio
4
- from typing import List, Generator, Any, Dict, Union
5
  from langchain_openai import ChatOpenAI
6
  import queue
7
  import threading
@@ -10,22 +10,17 @@ import os
10
  class AgentMessageQueue:
11
  def __init__(self):
12
  self.message_queue = queue.Queue()
13
- self.final_output = None
14
-
15
  def add_message(self, message: Dict):
 
16
  self.message_queue.put(message)
17
-
18
  def get_messages(self) -> List[Dict]:
19
  messages = []
20
  while not self.message_queue.empty():
21
  messages.append(self.message_queue.get())
22
  return messages
23
-
24
- def set_final_output(self, output: str):
25
- self.final_output = output
26
-
27
- def get_final_output(self) -> str:
28
- return self.final_output
29
 
30
  class ArticleCrew:
31
  def __init__(self, api_key: str = None):
@@ -34,264 +29,283 @@ class ArticleCrew:
34
  self.planner = None
35
  self.writer = None
36
  self.editor = None
37
-
 
 
38
  def initialize_agents(self, topic: str):
39
  if not self.api_key:
40
  raise ValueError("OpenAI API key is required")
41
-
42
  os.environ["OPENAI_API_KEY"] = self.api_key
43
-
44
- llm = ChatOpenAI(
45
- temperature=0.7,
46
- model="gpt-4"
47
- )
48
-
49
  self.planner = Agent(
50
  role="Content Planner",
51
  goal=f"Plan engaging and factually accurate content on {topic}",
52
- backstory=f"You're working on planning a blog article about the topic: {topic}. "
53
- "You collect information that helps the audience learn something "
54
- "and make informed decisions.",
55
  allow_delegation=False,
56
  verbose=True,
57
  llm=llm
58
  )
59
-
60
  self.writer = Agent(
61
  role="Content Writer",
62
- goal=f"Write insightful and factually accurate opinion piece about the topic: {topic}",
63
- backstory=f"You're working on writing a new opinion piece about the topic: {topic}. "
64
- "You base your writing on the work of the Content Planner.",
65
  allow_delegation=False,
66
  verbose=True,
67
  llm=llm
68
  )
69
-
70
  self.editor = Agent(
71
  role="Editor",
72
- goal="Edit a given blog post to align with the writing style",
73
- backstory="You are an editor who receives a blog post from the Content Writer.",
74
  allow_delegation=False,
75
  verbose=True,
76
  llm=llm
77
  )
78
 
79
- def create_tasks(self, topic: str):
80
- if not self.planner or not self.writer or not self.editor:
81
- self.initialize_agents(topic)
82
-
83
- plan_task = Task(
84
- description=(
85
- f"1. Prioritize the latest trends, key players, and noteworthy news on {topic}.\n"
86
- f"2. Identify the target audience, considering their interests and pain points.\n"
87
- f"3. Develop a detailed content outline including introduction, key points, and call to action.\n"
88
- f"4. Include SEO keywords and relevant data or sources."
89
- ),
90
- expected_output="A comprehensive content plan document with an outline, audience analysis, SEO keywords, and resources.",
91
  agent=self.planner
92
  )
93
 
94
- write_task = Task(
95
- description=(
96
- "1. Use the content plan to craft a compelling blog post.\n"
97
- "2. Incorporate SEO keywords naturally.\n"
98
- "3. Sections/Subtitles are properly named in an engaging manner.\n"
99
- "4. Ensure proper structure with introduction, body, and conclusion.\n"
100
- "5. Proofread for grammatical errors."
101
- ),
102
- expected_output="A well-written blog post in markdown format, ready for publication.",
103
  agent=self.writer
104
  )
105
 
106
- edit_task = Task(
107
- description="Proofread the given blog post for grammatical errors and alignment with the brand's voice.",
108
- expected_output="A well-written blog post in markdown format, ready for publication.",
 
 
 
 
109
  agent=self.editor
110
  )
111
 
112
- return [plan_task, write_task, edit_task]
113
 
114
  async def process_article(self, topic: str) -> Generator[List[Dict], None, None]:
115
- def step_callback(output: Any) -> None:
116
- try:
117
- output_str = str(output).strip()
118
-
119
- # Extract agent name
120
- if "# Agent:" in output_str:
121
- agent_name = output_str.split("# Agent:")[1].split("\n")[0].strip()
122
- else:
123
- agent_name = "Agent"
124
-
125
- # Extract task or final answer
126
- if "## Task:" in output_str:
127
- content = output_str.split("## Task:")[1].split("\n#")[0].strip()
128
- self.message_queue.add_message({
129
- "role": "assistant",
130
- "content": content,
131
- "metadata": {"title": f"πŸ“‹ {agent_name}'s Task"}
132
- })
133
- elif "## Final Answer:" in output_str:
134
- content = output_str.split("## Final Answer:")[1].strip()
135
- if agent_name == "Editor":
136
- # For Editor's final answer, store it for later
137
- self.message_queue.set_final_output(content)
138
- self.message_queue.add_message({
139
- "role": "assistant",
140
- "content": content,
141
- "metadata": {"title": f"βœ… {agent_name}'s Output"}
142
- })
143
- else:
144
- self.message_queue.add_message({
145
- "role": "assistant",
146
- "content": output_str,
147
- "metadata": {"title": f"πŸ’­ {agent_name} thinking"}
148
- })
149
-
150
- except Exception as e:
151
- print(f"Error in step_callback: {str(e)}")
152
 
153
- def task_callback(output: Any) -> None:
154
- try:
155
- content = str(output)
156
- if hasattr(output, 'agent'):
157
- agent_name = str(output.agent)
158
- else:
159
- agent_name = "Agent"
160
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
  self.message_queue.add_message({
162
  "role": "assistant",
163
- "content": content.strip(),
164
- "metadata": {"title": f"βœ… Task completed by {agent_name}"}
165
  })
166
 
167
- # If this is the Editor's task completion, add the final article
168
- if agent_name == "Editor":
169
- final_content = self.message_queue.get_final_output()
170
- if final_content:
171
- self.message_queue.add_message({
172
- "role": "assistant",
173
- "content": "Here's your completed article:",
174
- "metadata": {"title": "πŸ“ Final Article"}
175
- })
176
- self.message_queue.add_message({
177
- "role": "assistant",
178
- "content": final_content
179
- })
180
- self.message_queue.add_message({
181
- "role": "assistant",
182
- "content": "Article generation completed!",
183
- "metadata": {"title": "✨ Complete"}
184
- })
185
 
186
- except Exception as e:
187
- print(f"Error in task_callback: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
188
 
189
- self.initialize_agents(topic)
190
-
191
- crew = Crew(
192
- agents=[self.planner, self.writer, self.editor],
193
- tasks=self.create_tasks(topic),
194
- verbose=True,
195
- step_callback=step_callback,
196
- task_callback=task_callback
197
- )
198
-
199
- # Start notification
200
- yield [{
201
- "role": "assistant",
202
- "content": "Starting work on your article...",
203
- "metadata": {"title": "πŸš€ Process Started"}
204
- }]
205
-
206
- # Run crew in a separate thread
207
- result_container = []
208
- def run_crew():
209
- try:
210
- result = crew.kickoff(inputs={"topic": topic})
211
- result_container.append(result)
212
- except Exception as e:
213
- result_container.append(e)
214
- print(f"Error occurred: {str(e)}")
215
-
216
- thread = threading.Thread(target=run_crew)
217
- thread.start()
218
-
219
- # Stream messages while crew is working
220
- while thread.is_alive() or not self.message_queue.message_queue.empty():
221
- messages = self.message_queue.get_messages()
222
- if messages:
223
- yield messages
224
- await asyncio.sleep(0.1)
225
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
  def create_demo():
227
  article_crew = None
228
-
229
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
230
  gr.Markdown("# πŸ“ AI Article Writing Crew")
231
- gr.Markdown("Watch as this AI Crew collaborates to create your article! This application utilizes [CrewAI](https://www.crewai.com/) agents: Content Planner, Content Writer, and Content Editor, to write an article on any topic you choose. To get started, enter your OpenAI API Key below and press Enter!")
232
 
233
  openai_api_key = gr.Textbox(
234
- label='OpenAI API Key',
235
- type='password',
236
- placeholder='Type your OpenAI API key and press Enter!',
237
  interactive=True
238
  )
239
 
240
  chatbot = gr.Chatbot(
241
  label="Writing Process",
242
- avatar_images=(None, "https://avatars.githubusercontent.com/u/170677839?v=4"),
243
  height=700,
244
  type="messages",
245
  show_label=True,
246
  visible=False,
247
- value=[]
 
248
  )
249
-
250
  with gr.Row(equal_height=True):
251
  topic = gr.Textbox(
252
  label="Article Topic",
253
- placeholder="Enter the topic you want an article about...",
254
  scale=4,
255
  visible=False
256
  )
257
-
258
- async def process_input(topic, history, api_key):
259
- nonlocal article_crew
260
- if not api_key:
261
- history.append({
262
- "role": "assistant",
263
- "content": "Please provide an OpenAI API key first.",
264
- "metadata": {"title": "❌ Error"}
265
- })
266
- yield history # Changed from return to yield
267
- return # Early return without value
268
-
269
- # Initialize or update ArticleCrew with API key
270
- if article_crew is None:
271
- article_crew = ArticleCrew(api_key=api_key)
272
- else:
273
- article_crew.api_key = api_key
274
-
275
- # Add user message
276
  history.append({
277
- "role": "user",
278
- "content": f"Write an article about: {topic}"
 
279
  })
280
  yield history
281
-
282
- try:
283
- async for messages in article_crew.process_article(topic):
284
- history.extend(messages)
285
- yield history
286
- except Exception as e:
287
- history.append({
288
- "role": "assistant",
289
- "content": f"An error occurred: {str(e)}",
290
- "metadata": {"title": "❌ Error"}
291
- })
 
292
  yield history
293
-
294
- btn = gr.Button("Write Article", variant="primary", scale=1, visible=False)
 
 
 
 
 
295
 
296
  def show_interface():
297
  return {
@@ -301,17 +315,8 @@ def create_demo():
301
  btn: gr.Button(visible=True)
302
  }
303
 
304
- openai_api_key.submit(
305
- show_interface,
306
- None,
307
- [openai_api_key, chatbot, topic, btn]
308
- )
309
-
310
- btn.click(
311
- process_input,
312
- inputs=[topic, chatbot, openai_api_key], # Added openai_api_key back as input
313
- outputs=[chatbot]
314
- )
315
 
316
  return demo
317
 
 
1
  from crewai import Agent, Task, Crew
2
  import gradio as gr
3
  import asyncio
4
+ from typing import List, Dict, Any, Generator
5
  from langchain_openai import ChatOpenAI
6
  import queue
7
  import threading
 
10
  class AgentMessageQueue:
11
  def __init__(self):
12
  self.message_queue = queue.Queue()
13
+ self.last_agent = None
14
+
15
  def add_message(self, message: Dict):
16
+ print(f"Adding message to queue: {message}") # Debug print
17
  self.message_queue.put(message)
18
+
19
  def get_messages(self) -> List[Dict]:
20
  messages = []
21
  while not self.message_queue.empty():
22
  messages.append(self.message_queue.get())
23
  return messages
 
 
 
 
 
 
24
 
25
  class ArticleCrew:
26
  def __init__(self, api_key: str = None):
 
29
  self.planner = None
30
  self.writer = None
31
  self.editor = None
32
+ self.current_agent = None
33
+ self.final_article = None
34
+
35
  def initialize_agents(self, topic: str):
36
  if not self.api_key:
37
  raise ValueError("OpenAI API key is required")
38
+
39
  os.environ["OPENAI_API_KEY"] = self.api_key
40
+ llm = ChatOpenAI(temperature=0.7, model="gpt-4")
41
+
 
 
 
 
42
  self.planner = Agent(
43
  role="Content Planner",
44
  goal=f"Plan engaging and factually accurate content on {topic}",
45
+ backstory="Expert content planner with focus on creating engaging outlines",
 
 
46
  allow_delegation=False,
47
  verbose=True,
48
  llm=llm
49
  )
50
+
51
  self.writer = Agent(
52
  role="Content Writer",
53
+ goal=f"Write insightful and factually accurate piece about {topic}",
54
+ backstory="Expert content writer with focus on engaging articles",
 
55
  allow_delegation=False,
56
  verbose=True,
57
  llm=llm
58
  )
59
+
60
  self.editor = Agent(
61
  role="Editor",
62
+ goal="Polish and refine the article",
63
+ backstory="Expert editor with eye for detail and clarity",
64
  allow_delegation=False,
65
  verbose=True,
66
  llm=llm
67
  )
68
 
69
+ def create_tasks(self, topic: str) -> List[Task]:
70
+ planner_task = Task(
71
+ description=f"""Create a detailed content plan for an article about {topic} by:
72
+ 1. Prioritizing the latest trends, key players, and noteworthy news
73
+ 2. Identifying the target audience, considering their interests and pain points
74
+ 3. Developing a detailed content outline including introduction, key points, and call to action
75
+ 4. Including SEO keywords and relevant data or sources""",
76
+ expected_output="A comprehensive content plan with outline, keywords, and target audience analysis",
 
 
 
 
77
  agent=self.planner
78
  )
79
 
80
+ writer_task = Task(
81
+ description="""Based on the provided content plan:
82
+ 1. Use the content plan to craft a compelling blog post
83
+ 2. Incorporate SEO keywords naturally
84
+ 3. Ensure sections/subtitles are properly named in an engaging manner
85
+ 4. Create proper structure with introduction, body, and conclusion
86
+ 5. Proofread for grammatical errors""",
87
+ expected_output="A well-written article draft following the content plan",
 
88
  agent=self.writer
89
  )
90
 
91
+ editor_task = Task(
92
+ description="""Review the written article by:
93
+ 1. Checking for clarity and coherence
94
+ 2. Correcting any grammatical errors and typos
95
+ 3. Ensuring consistent tone and style
96
+ 4. Verifying proper formatting and structure""",
97
+ expected_output="A polished, final version of the article ready for publication",
98
  agent=self.editor
99
  )
100
 
101
+ return [planner_task, writer_task, editor_task]
102
 
103
  async def process_article(self, topic: str) -> Generator[List[Dict], None, None]:
104
+ def add_agent_messages(agent_name: str, tasks: str, emoji: str = "πŸ€–"):
105
+ # Add agent header
106
+ self.message_queue.add_message({
107
+ "role": "assistant",
108
+ "content": agent_name,
109
+ "metadata": {"title": f"{emoji} {agent_name}"}
110
+ })
111
+
112
+ # Add task description
113
+ self.message_queue.add_message({
114
+ "role": "assistant",
115
+ "content": tasks,
116
+ "metadata": {"title": f"πŸ“‹ Task for {agent_name}"}
117
+ })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
 
119
+ def setup_next_agent(current_agent: str) -> None:
120
+ agent_sequence = {
121
+ "Content Planner": ("Content Writer", """1. Use the content plan to craft a compelling blog post
122
+ 2. Incorporate SEO keywords naturally
123
+ 3. Ensure sections/subtitles are properly named in an engaging manner
124
+ 4. Create proper structure with introduction, body, and conclusion
125
+ 5. Proofread for grammatical errors"""),
126
 
127
+ "Content Writer": ("Editor", """1. Review the article for clarity and coherence
128
+ 2. Check for grammatical errors and typos
129
+ 3. Ensure consistent tone and style
130
+ 4. Verify proper formatting and structure""")
131
+ }
132
+
133
+ if current_agent in agent_sequence:
134
+ next_agent, tasks = agent_sequence[current_agent]
135
+ self.current_agent = next_agent
136
+ add_agent_messages(next_agent, tasks)
137
+
138
+
139
+ def task_callback(task_output) -> None:
140
+ print(f"Task callback received: {task_output}") # Debug print
141
+
142
+ # Extract content from raw output
143
+ raw_output = task_output.raw
144
+ if "## Final Answer:" in raw_output:
145
+ content = raw_output.split("## Final Answer:")[1].strip()
146
+ else:
147
+ content = raw_output.strip()
148
+
149
+ # Handle the output based on current agent
150
+ if self.current_agent == "Editor":
151
+ # Not going to show editor's output with metadata
152
+ # Instead, I am showing assistant message as the final article
153
  self.message_queue.add_message({
154
  "role": "assistant",
155
+ "content": "Final article is ready!",
156
+ "metadata": {"title": "πŸ“ Final Article"}
157
  })
158
 
159
+ # Convert common markdown patterns to Gradio-compatible markdown
160
+ formatted_content = content
161
+ # Ensure proper spacing for headers
162
+ formatted_content = formatted_content.replace("\n#", "\n\n#")
163
+ # Ensure proper spacing for lists
164
+ formatted_content = formatted_content.replace("\n-", "\n\n-")
165
+ formatted_content = formatted_content.replace("\n*", "\n\n*")
166
+ formatted_content = formatted_content.replace("\n1.", "\n\n1.")
167
+ # Ensure proper spacing for paragraphs
168
+ formatted_content = formatted_content.replace("\n\n\n", "\n\n")
 
 
 
 
 
 
 
 
169
 
170
+ # Add the final article content without metadata
171
+ self.message_queue.add_message({
172
+ "role": "assistant",
173
+ "content": formatted_content
174
+ })
175
+ else:
176
+ # For other agents, show their output with metadata
177
+ self.message_queue.add_message({
178
+ "role": "assistant",
179
+ "content": content,
180
+ "metadata": {"title": f"✨ Output from {self.current_agent}"}
181
+ })
182
+ # Setup next agent
183
+ setup_next_agent(self.current_agent)
184
 
185
+ def step_callback(output: Any) -> None:
186
+ print(f"Step callback received: {output}") # Debug print
187
+ # We'll only use step_callback for logging purposes now
188
+ pass
189
+
190
+ try:
191
+ self.initialize_agents(topic)
192
+ self.current_agent = "Content Planner"
193
+
194
+ # Start process
195
+ yield [{
196
+ "role": "assistant",
197
+ "content": "Starting work on your article...",
198
+ "metadata": {"title": "πŸš€ Process Started"}
199
+ }]
200
+
201
+ # Initialize first agent
202
+ add_agent_messages("Content Planner",
203
+ """1. Prioritize the latest trends, key players, and noteworthy news
204
+ 2. Identify the target audience, considering their interests and pain points
205
+ 3. Develop a detailed content outline including introduction, key points, and call to action
206
+ 4. Include SEO keywords and relevant data or sources""")
207
+
208
+ crew = Crew(
209
+ agents=[self.planner, self.writer, self.editor],
210
+ tasks=self.create_tasks(topic),
211
+ verbose=True,
212
+ step_callback=step_callback,
213
+ task_callback=task_callback
214
+ )
 
 
 
 
 
 
215
 
216
+ def run_crew():
217
+ try:
218
+ crew.kickoff()
219
+ except Exception as e:
220
+ print(f"Error in crew execution: {str(e)}") # Debug print
221
+ self.message_queue.add_message({
222
+ "role": "assistant",
223
+ "content": f"An error occurred: {str(e)}",
224
+ "metadata": {"title": "❌ Error"}
225
+ })
226
+
227
+ thread = threading.Thread(target=run_crew)
228
+ thread.start()
229
+
230
+ while thread.is_alive() or not self.message_queue.message_queue.empty():
231
+ messages = self.message_queue.get_messages()
232
+ if messages:
233
+ print(f"Yielding messages: {messages}") # Debug print
234
+ yield messages
235
+ await asyncio.sleep(0.1)
236
+
237
+ except Exception as e:
238
+ print(f"Error in process_article: {str(e)}") # Debug print
239
+ yield [{
240
+ "role": "assistant",
241
+ "content": f"An error occurred: {str(e)}",
242
+ "metadata": {"title": "❌ Error"}
243
+ }]
244
+
245
+
246
+ # Gradio code
247
  def create_demo():
248
  article_crew = None
249
+
250
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
251
  gr.Markdown("# πŸ“ AI Article Writing Crew")
 
252
 
253
  openai_api_key = gr.Textbox(
254
+ label='OpenAI API Key',
255
+ type='password',
256
+ placeholder='Enter your OpenAI API key...',
257
  interactive=True
258
  )
259
 
260
  chatbot = gr.Chatbot(
261
  label="Writing Process",
 
262
  height=700,
263
  type="messages",
264
  show_label=True,
265
  visible=False,
266
+ bubble_full_width=False, # Allow messages to wrap naturally
267
+ render_markdown=True # Enable markdown rendering
268
  )
269
+
270
  with gr.Row(equal_height=True):
271
  topic = gr.Textbox(
272
  label="Article Topic",
273
+ placeholder="Enter topic...",
274
  scale=4,
275
  visible=False
276
  )
277
+ btn = gr.Button("Write Article", variant="primary", scale=1, visible=False)
278
+
279
+ async def process_input(topic, history, api_key):
280
+ nonlocal article_crew
281
+ if not api_key:
282
+ history = history or []
 
 
 
 
 
 
 
 
 
 
 
 
 
283
  history.append({
284
+ "role": "assistant",
285
+ "content": "Please provide an OpenAI API key.",
286
+ "metadata": {"title": "❌ Error"}
287
  })
288
  yield history
289
+ return
290
+
291
+ if article_crew is None:
292
+ article_crew = ArticleCrew(api_key=api_key)
293
+
294
+ history = history or []
295
+ history.append({"role": "user", "content": f"Write an article about: {topic}"})
296
+ yield history
297
+
298
+ try:
299
+ async for messages in article_crew.process_article(topic):
300
+ history.extend(messages)
301
  yield history
302
+ except Exception as e:
303
+ history.append({
304
+ "role": "assistant",
305
+ "content": f"An error occurred: {str(e)}",
306
+ "metadata": {"title": "❌ Error"}
307
+ })
308
+ yield history
309
 
310
  def show_interface():
311
  return {
 
315
  btn: gr.Button(visible=True)
316
  }
317
 
318
+ openai_api_key.submit(show_interface, None, [openai_api_key, chatbot, topic, btn])
319
+ btn.click(process_input, [topic, chatbot, openai_api_key], [chatbot])
 
 
 
 
 
 
 
 
 
320
 
321
  return demo
322