ysharma HF staff commited on
Commit
dc93c04
Β·
verified Β·
1 Parent(s): 2aee27c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +175 -172
app.py CHANGED
@@ -1,140 +1,55 @@
1
  from crewai import Agent, Task, Crew
2
  import gradio as gr
3
- from gradio import ChatMessage
4
  import asyncio
5
- import re
6
- import sys
7
- from typing import List, Generator
8
- import os
9
- from dotenv import load_dotenv
10
- import threading
11
  from langchain_openai import ChatOpenAI
 
 
 
12
 
13
- class OutputParser:
14
  def __init__(self):
15
- self.buffer = ""
16
- self.current_agent = None
17
- self.final_article_sent = False
18
- self.message_queue = {
19
- "Content Planner": [],
20
- "Content Writer": [],
21
- "Editor": []
22
- }
23
- self.agent_sequence = ["Content Planner", "Content Writer", "Editor"]
24
 
25
- def format_output(self, raw_content: str, agent_name: str) -> str:
26
- """Format the output content based on agent type."""
27
- if agent_name == "Content Planner":
28
- # Clean up the planner's output to make it more readable
29
- lines = raw_content.split('\n')
30
- formatted_lines = []
31
- for line in lines:
32
- # Remove number prefixes and clean up
33
- line = re.sub(r'^\d+\.\s*', '', line.strip())
34
- # Make text size normal by removing markdown formatting
35
- line = re.sub(r'^#+\s*', '', line)
36
- if line:
37
- formatted_lines.append(line)
38
- return '\n\n'.join(formatted_lines)
39
 
40
- elif agent_name == "Content Writer":
41
- # Clean up writer's output to make it more readable
42
- # Remove markdown headers but keep the text
43
- content = re.sub(r'^#+\s*(.+)$', r'\1', raw_content, flags=re.MULTILINE)
44
- # Remove multiple newlines
45
- content = re.sub(r'\n{3,}', '\n\n', content)
46
- return content.strip()
47
-
48
- return raw_content.strip()
49
-
50
- def parse_output(self, text: str) -> List[ChatMessage]:
51
  messages = []
52
- cleaned_text = re.sub(r'\x1B\[[0-9;]*[mK]', '', text)
53
-
54
- # Look for working agent declarations
55
- agent_match = re.search(r'\[DEBUG\]: == Working Agent: (.*?)(?=\n|$)', cleaned_text)
56
- if agent_match:
57
- self.current_agent = agent_match.group(1)
58
- self.message_queue[self.current_agent].append(ChatMessage(
59
- role="assistant",
60
- content=f"Starting work...",
61
- metadata={"title": f"πŸ€– {self.current_agent}"}
62
- ))
63
-
64
- # Look for task information
65
- task_match = re.search(r'\[INFO\]: == Starting Task: (.*?)(?=\n\n|\n> Entering|$)', cleaned_text, re.DOTALL)
66
- if task_match and self.current_agent:
67
- task_content = task_match.group(1).strip()
68
- self.message_queue[self.current_agent].append(ChatMessage(
69
- role="assistant",
70
- content=task_content,
71
- metadata={"title": f"πŸ“‹ Task for {self.current_agent}"}
72
- ))
73
-
74
- # Look for agent outputs in debug messages
75
- debug_match = re.search(r'\[DEBUG\]: == \[(.*?)\] Task output: (.*?)(?=\[DEBUG\]|$)', cleaned_text, re.DOTALL)
76
- if debug_match:
77
- agent_name = debug_match.group(1)
78
- output_content = debug_match.group(2).strip()
79
-
80
- # Format the output content
81
- formatted_content = self.format_output(output_content, agent_name)
82
-
83
- if agent_name == "Editor" and not self.final_article_sent:
84
- self.message_queue[agent_name].append(ChatMessage(
85
- role="assistant",
86
- content="Final article is ready!",
87
- metadata={"title": "πŸ“ Final Article"}
88
- ))
89
- self.message_queue[agent_name].append(ChatMessage(
90
- role="assistant",
91
- content=formatted_content
92
- ))
93
- self.final_article_sent = True
94
- elif agent_name != "Editor":
95
- self.message_queue[agent_name].append(ChatMessage(
96
- role="assistant",
97
- content=formatted_content,
98
- metadata={"title": f"πŸ’‘ Output from {agent_name}"}
99
- ))
100
-
101
- # Return messages in the correct sequence
102
- for agent in self.agent_sequence:
103
- if self.message_queue[agent]:
104
- messages.extend(self.message_queue[agent])
105
- self.message_queue[agent] = []
106
-
107
  return messages
108
-
109
- class StreamingCapture:
110
- def __init__(self):
111
- self.buffer = ""
112
 
113
- def write(self, text):
114
- self.buffer += text
115
- return len(text)
116
 
117
- def flush(self):
118
- pass
119
 
120
  class ArticleCrew:
121
  def __init__(self, api_key: str = None):
122
  self.api_key = api_key
123
- self.initialize_agents()
 
 
 
124
 
125
- def initialize_agents(self):
126
- # Create a ChatOpenAI instance with the API key
 
 
 
 
127
  llm = ChatOpenAI(
128
- openai_api_key=self.api_key,
129
  temperature=0.7,
130
  model="gpt-4"
131
  )
132
 
133
- # Initialize agents with the LLM
134
  self.planner = Agent(
135
  role="Content Planner",
136
- goal="Plan engaging and factually accurate content on {topic}",
137
- backstory="You're working on planning a blog article about the topic: {topic}. "
138
  "You collect information that helps the audience learn something "
139
  "and make informed decisions.",
140
  allow_delegation=False,
@@ -144,8 +59,8 @@ class ArticleCrew:
144
 
145
  self.writer = Agent(
146
  role="Content Writer",
147
- goal="Write insightful and factually accurate opinion piece about the topic: {topic}",
148
- backstory="You're working on writing a new opinion piece about the topic: {topic}. "
149
  "You base your writing on the work of the Content Planner.",
150
  allow_delegation=False,
151
  verbose=True,
@@ -160,10 +75,11 @@ class ArticleCrew:
160
  verbose=True,
161
  llm=llm
162
  )
163
-
164
- self.output_parser = OutputParser()
165
 
166
  def create_tasks(self, topic: str):
 
 
 
167
  plan_task = Task(
168
  description=(
169
  f"1. Prioritize the latest trends, key players, and noteworthy news on {topic}.\n"
@@ -195,68 +111,131 @@ class ArticleCrew:
195
 
196
  return [plan_task, write_task, edit_task]
197
 
198
- async def process_article(self, topic: str) -> Generator[List[ChatMessage], None, None]:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
  crew = Crew(
200
  agents=[self.planner, self.writer, self.editor],
201
  tasks=self.create_tasks(topic),
202
- verbose=2
 
 
203
  )
204
-
205
- capture = StreamingCapture()
206
- original_stdout = sys.stdout
207
- sys.stdout = capture
208
 
209
- try:
210
- # Start the crew task in a separate thread to not block streaming
211
- result_container = []
212
-
213
- def run_crew():
214
- try:
215
- result = crew.kickoff(inputs={"topic": topic})
216
- result_container.append(result)
217
- except Exception as e:
218
- result_container.append(e)
219
-
220
- thread = threading.Thread(target=run_crew)
221
- thread.start()
222
-
223
- # Stream output while the crew is working
224
- last_processed = 0
225
- while thread.is_alive() or last_processed < len(capture.buffer):
226
- if len(capture.buffer) > last_processed:
227
- new_content = capture.buffer[last_processed:]
228
- messages = self.output_parser.parse_output(new_content)
229
- if messages:
230
- for msg in messages:
231
- yield [msg]
232
- last_processed = len(capture.buffer)
233
- await asyncio.sleep(0.1)
234
-
235
- # Check if we got a result or an error
236
- if result_container and not isinstance(result_container[0], Exception):
237
- # Final messages already sent by the parser
238
- pass
239
- else:
240
- yield [ChatMessage(
241
- role="assistant",
242
- content="An error occurred while generating the article.",
243
- metadata={"title": "❌ Error"}
244
- )]
245
 
246
- finally:
247
- sys.stdout = original_stdout
 
 
 
 
248
 
249
  def create_demo():
250
- article_crew = None # Initialize as None
251
 
252
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
253
  gr.Markdown("# πŸ“ AI Article Writing Crew")
254
  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!")
 
255
  openai_api_key = gr.Textbox(
256
  label='OpenAI API Key',
257
  type='password',
258
  placeholder='Type your OpenAI API key and press Enter!',
259
- interactive=True)
 
260
 
261
  chatbot = gr.Chatbot(
262
  label="Writing Process",
@@ -264,7 +243,8 @@ def create_demo():
264
  height=700,
265
  type="messages",
266
  show_label=True,
267
- visible=False
 
268
  )
269
 
270
  with gr.Row(equal_height=True):
@@ -275,17 +255,40 @@ def create_demo():
275
  visible=False
276
  )
277
 
278
- async def process_input(topic, history, openai_api_key):
279
  nonlocal article_crew
280
- # Initialize ArticleCrew with the API key if not already initialized
 
 
 
 
 
 
 
 
 
281
  if article_crew is None:
282
- article_crew = ArticleCrew(api_key=openai_api_key)
 
 
283
 
284
- history.append(ChatMessage(role="user", content=f"Write an article about: {topic}"))
 
 
 
 
285
  yield history
286
 
287
- async for messages in article_crew.process_article(topic):
288
- history.extend(messages)
 
 
 
 
 
 
 
 
289
  yield history
290
 
291
  btn = gr.Button("Write Article", variant="primary", scale=1, visible=False)
@@ -306,7 +309,7 @@ def create_demo():
306
 
307
  btn.click(
308
  process_input,
309
- inputs=[topic, chatbot, openai_api_key],
310
  outputs=[chatbot]
311
  )
312
 
@@ -315,4 +318,4 @@ def create_demo():
315
  if __name__ == "__main__":
316
  demo = create_demo()
317
  demo.queue()
318
- demo.launch()
 
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
8
+ import os
9
 
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):
32
  self.api_key = api_key
33
+ self.message_queue = AgentMessageQueue()
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,
 
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,
 
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"
 
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",
 
243
  height=700,
244
  type="messages",
245
  show_label=True,
246
+ visible=False,
247
+ value=[]
248
  )
249
 
250
  with gr.Row(equal_height=True):
 
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)
 
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
 
 
318
  if __name__ == "__main__":
319
  demo = create_demo()
320
  demo.queue()
321
+ demo.launch(debug=True)