CultriX commited on
Commit
45727ee
·
verified ·
1 Parent(s): ac407f6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +150 -242
app.py CHANGED
@@ -1,56 +1,19 @@
1
- import os
2
- import sys
3
- import asyncio
4
  import logging
5
- import threading
6
  import queue
7
- import gradio as gr
8
  import httpx
9
- from typing import Generator, Any, Dict, List
10
 
11
- # -------------------- Configuration --------------------
12
- logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
13
 
14
- # -------------------- External Model Call --------------------
15
- async def call_model(prompt: str, model: str = "gpt-4o-mini", api_key: str = None) -> str:
16
- """
17
- Sends a prompt to the OpenAI API endpoint using the specified model (overridden to gpt-4o-mini)
18
- and returns the generated response.
19
- """
20
- # Use the provided API key or fall back to the environment variable
21
- if api_key is None:
22
- api_key = os.getenv("OPENAI_API_KEY")
23
- url = "https://api.openai.com/v1/chat/completions"
24
- headers = {
25
- "Authorization": f"Bearer {api_key}",
26
- "Content-Type": "application/json"
27
- }
28
- # Override the model value to always be "gpt-4o-mini"
29
- payload = {
30
- "model": "gpt-4o-mini",
31
- "messages": [{"role": "user", "content": prompt}],
32
- }
33
- async with httpx.AsyncClient(timeout=httpx.Timeout(300.0)) as client:
34
- response = await client.post(url, headers=headers, json=payload)
35
- response.raise_for_status()
36
- response_json = response.json()
37
- return response_json["choices"][0]["message"]["content"]
38
 
39
- # -------------------- Agent Classes --------------------
40
- class PromptOptimizerAgent:
41
- async def optimize_prompt(self, user_prompt: str, api_key: str) -> str:
42
- """
43
- Optimizes the user's initial prompt according to the following instructions:
44
- >>> Given the user's initial prompt below the ### characters please enhance it.
45
- 1. Start with clear, precise instructions placed at the beginning of the prompt.
46
- 2. Include specific details about the desired context, outcome, length, format, and style.
47
- 3. Provide examples of the desired output format, if possible.
48
- 4. Use appropriate leading words or phrases to guide the desired output, especially if code generation is involved.
49
- 5. Avoid any vague or imprecise language.
50
- 6. Rather than only stating what not to do, provide guidance on what should be done instead.
51
- Remember to ensure the revised prompt remains true to the user's original intent. <<<
52
- ###User initial prompt below ###
53
- """
54
  system_prompt = (
55
  "Given the user's initial prompt below the ### characters please enhance it. "
56
  "1. Start with clear, precise instructions placed at the beginning of the prompt. "
@@ -60,210 +23,155 @@ class PromptOptimizerAgent:
60
  "5. Avoid any vague or imprecise language. "
61
  "6. Rather than only stating what not to do, provide guidance on what should be done instead. "
62
  "Remember to ensure the revised prompt remains true to the user's original intent. "
63
- "###User initial prompt ###"
64
  )
65
- full_prompt = f"{system_prompt}\n{user_prompt}\n<<<"
66
- optimized = await call_model(full_prompt, api_key=api_key)
67
- return optimized
68
-
69
- class OrchestratorAgent:
70
- def __init__(self, log_queue: queue.Queue) -> None:
71
- self.log_queue = log_queue
72
-
73
- async def generate_plan(self, task: str, api_key: str) -> str:
74
- """
75
- Generates a detailed, step-by-step plan for completing the given task.
76
- """
77
- prompt = (
78
- f"You are an orchestrator agent. The user has provided the task: '{task}'.\n"
79
- "Generate a detailed, step-by-step plan for completing this task by coordinating a coder agent, "
80
- "a code reviewer agent, and a documentation agent. List the steps as bullet points."
81
- )
82
- plan = await call_model(prompt, api_key=api_key)
83
- return plan
84
-
85
- class CoderAgent:
86
- async def generate_code(self, instructions: str, api_key: str) -> str:
87
- """
88
- Generates code based on the given instructions.
89
- """
90
- prompt = (
91
- "You are a coder agent. Based on the following instructions, generate the requested code. "
92
- "Only output the generated code, never any explanations or any other information besides the actual code!\n"
93
- f"{instructions}\n"
94
  )
95
- code = await call_model(prompt, api_key=api_key)
96
- return code
97
-
98
- class CodeReviewerAgent:
99
- async def review_code(self, code: str, task: str, api_key: str) -> str:
100
- """
101
- Reviews the provided code to check if it meets the task specifications.
102
- NEVER generate any code yourself! Respond only with feedback or with 'APPROVE' if everything is correct.
103
- """
104
- prompt = (
105
- "You are a code reviewing agent highly skilled in evaluating code quality. "
106
- "Review the provided code and check if it meets the task specifications and properly addresses any provided feedback. "
107
- "NEVER generate any code yourself! Respond only with feedback or with 'APPROVE' if everything is correct. "
108
- "Do not mention 'APPROVE' before actually approving! Do not request documentation or user guides:\n"
109
- f"Task: {task}\n"
110
- f"Code:\n{code}\n\n"
111
- )
112
- review = await call_model(prompt, api_key=api_key)
113
- return review
114
-
115
- class DocumentationAgent:
116
- async def generate_documentation(self, code: str, api_key: str) -> str:
117
- """
118
- Generates clear and concise documentation for the approved code,
119
- including a brief and concise --help message.
120
- """
121
- prompt = (
122
- "You are a documentation agent. Generate a brief, clear and concise documentation for the following approved code. "
123
- "Keep it short and compact, focusing on the main elements, do not include unnecessary extras that limit readability. "
124
- "Additionally, generate a brief and concise --help message for the code:\n"
125
- f"{code}\n"
126
- "Briefly explain what the code does and how it works. Make sure to be clear and concise, do not include unnecessary extras that limit readability."
127
- )
128
- documentation = await call_model(prompt, api_key=api_key)
129
- return documentation
130
-
131
- # -------------------- Multi-Agent Conversation --------------------
132
- async def multi_agent_conversation(task_message: str, log_queue: queue.Queue, api_key: str) -> None:
133
- """
134
- Conducts a multi-agent conversation where each agent's response is generated via the external model API.
135
- The conversation is logged to the provided queue.
136
- """
137
- conversation: List[Dict[str, str]] = [] # List to store each agent's message
138
-
139
- # Step 0: Use Prompt Optimizer to enhance the user's initial prompt.
140
- log_queue.put("[Prompt Optimizer]: Received initial task. Optimizing prompt...")
141
- prompt_optimizer = PromptOptimizerAgent()
142
- optimized_task = await prompt_optimizer.optimize_prompt(task_message, api_key=api_key)
143
- conversation.append({"agent": "Prompt Optimizer", "message": f"Optimized Task:\n{optimized_task}"})
144
- log_queue.put(f"[Prompt Optimizer]: Optimized task prompt:\n{optimized_task}")
145
-
146
- # Step 1: Orchestrator generates a plan based on the optimized task.
147
- log_queue.put("[Orchestrator]: Received optimized task. Generating plan...")
148
- orchestrator = OrchestratorAgent(log_queue)
149
- plan = await orchestrator.generate_plan(optimized_task, api_key=api_key)
150
- conversation.append({"agent": "Orchestrator", "message": f"Plan:\n{plan}"})
151
- log_queue.put(f"[Orchestrator]: Plan generated:\n{plan}")
152
-
153
- # Step 2: Coder generates code based on the plan.
154
- coder = CoderAgent()
155
- coder_instructions = f"Implement the task as described in the following plan:\n{plan}"
156
- log_queue.put("[Coder]: Received coding task from the Orchestrator.")
157
- code = await coder.generate_code(coder_instructions, api_key=api_key)
158
- conversation.append({"agent": "Coder", "message": f"Code:\n{code}"})
159
- log_queue.put(f"[Coder]: Code generated:\n{code}")
160
-
161
- # Step 3: Code Reviewer reviews the generated code.
162
- reviewer = CodeReviewerAgent()
163
- approval_keyword = "approve"
164
- revision_iteration = 0
165
- while True:
166
- if revision_iteration == 0:
167
- log_queue.put("[Code Reviewer]: Starting review of the generated code...")
168
- else:
169
- log_queue.put(f"[Code Reviewer]: Reviewing the revised code (Iteration {revision_iteration})...")
170
 
171
- review = await reviewer.review_code(code, optimized_task, api_key=api_key)
172
- conversation.append({"agent": "Code Reviewer", "message": f"Review (Iteration {revision_iteration}):\n{review}"})
173
- log_queue.put(f"[Code Reviewer]: Review feedback (Iteration {revision_iteration}):\n{review}")
 
174
 
175
- # Check if the code has been approved
176
- if approval_keyword in review.lower():
177
- log_queue.put("[Code Reviewer]: Code approved.")
178
- break # Exit the loop if approved
 
179
 
180
- # If not approved, increment the revision count.
181
- revision_iteration += 1
182
-
183
- # Kill-switch: After 5 generations without approval, shut down.
184
- if revision_iteration >= 5:
185
- log_queue.put("Unable to solve your task to full satisfaction :(")
186
- sys.exit("Unable to solve your task to full satisfaction :(")
187
-
188
- # If under the limit, instruct the coder to revise the code.
189
- log_queue.put(f"[Orchestrator]: Code not approved. Instructing coder to revise the code (Iteration {revision_iteration}).")
190
- update_instructions = f"Please revise the code according to the following feedback. Feedback: {review}"
191
- revised_code = await coder.generate_code(update_instructions, api_key=api_key)
192
- conversation.append({"agent": "Coder", "message": f"Revised Code (Iteration {revision_iteration}):\n{revised_code}"})
193
- log_queue.put(f"[Coder]: Revised code submitted (Iteration {revision_iteration}):\n{revised_code}")
194
- code = revised_code # Update the code for the next review iteration
195
-
196
- # Step 4: Documentation Agent generates documentation for the approved code.
197
- doc_agent = DocumentationAgent()
198
- log_queue.put("[Documentation Agent]: Generating documentation for the approved code.")
199
- documentation = await doc_agent.generate_documentation(code, api_key=api_key)
200
- conversation.append({"agent": "Documentation Agent", "message": f"Documentation:\n{documentation}"})
201
- log_queue.put(f"[Documentation Agent]: Documentation generated:\n{documentation}")
202
-
203
- log_queue.put("Multi-agent conversation complete.")
204
- log_queue.put(("result", conversation))
205
-
206
- # -------------------- Process Generator for Streaming --------------------
207
- def process_conversation_generator(task_message: str, api_key: str) -> Generator[str, None, None]:
208
- """
209
- Wraps the asynchronous multi-agent conversation and yields log messages as they are generated.
210
- """
211
- log_q: queue.Queue = queue.Queue()
212
-
213
- def run_conversation() -> None:
214
- asyncio.run(multi_agent_conversation(task_message, log_q, api_key))
215
-
216
- thread = threading.Thread(target=run_conversation)
217
- thread.start()
218
-
219
- final_result = None
220
- # Yield log messages as long as the thread is running or the queue is not empty.
221
- while thread.is_alive() or not log_q.empty():
222
- try:
223
  msg = log_q.get(timeout=0.1)
224
  if isinstance(msg, tuple) and msg[0] == "result":
225
  final_result = msg[1]
226
- yield "Final conversation complete."
227
- else:
228
- yield msg
229
- except queue.Empty:
230
- continue
231
-
232
- thread.join()
233
- if final_result:
234
- # Format the final conversation log.
235
- conv_text = "\n========== Multi-Agent Conversation ==========\n"
236
- for entry in final_result:
237
- conv_text += f"[{entry['agent']}]: {entry['message']}\n\n"
238
- yield conv_text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239
 
240
- # -------------------- Chat Function for Gradio --------------------
241
- def multi_agent_chat(message: str, history: List[Any], openai_api_key: str = None) -> Generator[str, None, None]:
242
- """
243
- Chat function for Gradio.
244
- The user's message is interpreted as the task description.
245
- An optional OpenAI API key can be provided via the additional input; if not provided, the environment variable is used.
246
- This function streams the multi-agent conversation log messages.
247
- """
248
- if not openai_api_key:
249
- openai_api_key = os.getenv("OPENAI_API_KEY")
250
- yield from process_conversation_generator(message, openai_api_key)
251
 
252
- # -------------------- Launch the Chatbot --------------------
253
- # Use Gradio's ChatInterface with an additional input field for the OpenAI API key.
254
- iface = gr.ChatInterface(
255
- fn=multi_agent_chat,
256
- additional_inputs=[gr.Textbox(label="OpenAI API Key (optional)", type="password", placeholder="Leave blank to use env variable")],
257
- type="messages",
258
- title="Actual Multi-Agent Conversation Chatbot",
259
- description="""
260
- - Collaborative workflow between Prompt Enhancer, Orchestrator, Coder, Code-Reviewer and Documentation Generator agents.
261
- - Enter a task and observe as your prompt gets magically solved! :)
262
- - NOTE: The full conversation log will be displayed at the end, showing all the steps taken!
263
- - NOTE2: If the Coder is unable to satisfactorily complete the task after five attempts, the script will terminate to prevent endless iterations.
264
- - NOTE3: You will have to input your OPENAI_API_KEY at the bottom of the page for this to work!
265
- """
266
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
267
 
268
  if __name__ == "__main__":
269
- iface.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from abc import ABC, abstractmethod
2
+ from typing import Generator, Any
 
3
  import logging
 
4
  import queue
 
5
  import httpx
6
+ from gradio import ChatInterface, gr
7
 
8
+ logger = logging.getLogger(__name__)
 
9
 
10
+ class Agent(ABC):
11
+ @abstractmethod
12
+ async def generate_response(self, prompt: str, api_key: str) -> str:
13
+ pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
+ class PromptOptimizerAgent(Agent):
16
+ async def generate_response(self, prompt: str, api_key: str) -> str:
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  system_prompt = (
18
  "Given the user's initial prompt below the ### characters please enhance it. "
19
  "1. Start with clear, precise instructions placed at the beginning of the prompt. "
 
23
  "5. Avoid any vague or imprecise language. "
24
  "6. Rather than only stating what not to do, provide guidance on what should be done instead. "
25
  "Remember to ensure the revised prompt remains true to the user's original intent. "
26
+ ###User initial prompt###
27
  )
28
+ return await call_openai(system_prompt, api_key)
29
+
30
+ class OrchestratorAgent(Agent):
31
+ async def generate_response(self, task_message: str, api_key: str) -> str:
32
+ plan = f"You are an orchestrator agent. The user has provided the task: '{task_message}'. Generate a detailed, step-by-step plan for completing this task by coordinating a coder agent, a code reviewer agent, and a documentation agent. List the steps as bullet points."
33
+ return await call_openai(plan, api_key)
34
+
35
+ class CoderAgent(Agent):
36
+ async def generate_response(self, instructions: str, api_key: str) -> str:
37
+ prompt = f"Implement the task as described in the following plan:\n{instructions}"
38
+ return await call_openai(prompt, api_key)
39
+
40
+ class CodeReviewerAgent(Agent):
41
+ async def generate_response(self, code: str, task: str, api_key: str) -> str:
42
+ feedback = await call_openai(
43
+ f"You are a code reviewer agent. Review the provided code: '{code}' and check if it meets the task specifications.",
44
+ api_key=api_key
 
 
 
 
 
 
 
 
 
 
 
 
45
  )
46
+ return feedback
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
 
48
+ class DocumentationAgent(Agent):
49
+ async def generate_response(self, code: str, api_key: str) -> str:
50
+ prompt = f"You are a documentation agent. Generate a brief documentation for the code:\nCode:\n{code}"
51
+ return await call_openai(prompt, api_key)
52
 
53
+ async def process_conversation_generator(conversation: list, log_q: queue.Queue, api_key: str) -> Generator[str, None, None]:
54
+ try:
55
+ while True:
56
+ if not conversation or not log_q.get(timeout=0.1):
57
+ continue
58
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  msg = log_q.get(timeout=0.1)
60
  if isinstance(msg, tuple) and msg[0] == "result":
61
  final_result = msg[1]
62
+ break
63
+
64
+ yield msg
65
+ except asyncio.CancelledError:
66
+ pass
67
+ finally:
68
+ if log_q.empty():
69
+ log_q.put("Final conversation complete.")
70
+
71
+ async def multi_agent_conversation(
72
+ task_message: str,
73
+ log_q: queue.Queue,
74
+ api_key: str,
75
+ additional_inputs=None
76
+ ) -> None:
77
+ if additional_inputs is None:
78
+ additional_inputs = [gr.Textbox(label="OpenAI API Key (optional)")]
79
+
80
+ agents = [
81
+ PromptOptimizerAgent(),
82
+ OrchestratorAgent(),
83
+ CoderAgent(),
84
+ CodeReviewerAgent(),
85
+ DocumentationAgent()
86
+ ]
87
+
88
+ log_queue = queue.Queue()
89
+ run_conversation = None
90
+
91
+ async def run_conversation_thread() -> None:
92
+ nonlocal run_conversation
93
+ try:
94
+ if run_conversation is not None:
95
+ await run_conversation
96
+ except asyncio.CancelledError:
97
+ pass
98
 
99
+ thread = asyncio.to_thread(run_conversation_thread)
100
+ thread.start()
 
 
 
 
 
 
 
 
 
101
 
102
+ try:
103
+ conversation = []
104
+ log_q.put("[Prompt Optimizer]: Received initial task. Optimizing prompt...")
105
+
106
+ # Step 0: Use Prompt Optimizer
107
+ optimized_task = await agents[0].generate_response(task_message, api_key)
108
+ conversation.append({"agent": "Prompt Optimizer", "message": f"Optimized Task:\n{optimized_task}"})
109
+ log_q.put(f"[Prompt Optimizer]: Optimized Task:\n{optimized_task}")
110
+
111
+ # Step 1: Generate Plan
112
+ plan = await agents[1].generate_response(optimized_task, api_key)
113
+ conversation.append({"agent": "Orchestrator", "message": f"Plan:\n{plan}"})
114
+ log_q.put(f"[Orchestrator]: Plan generated:\n{plan}")
115
+
116
+ # Step 2: Generate Code
117
+ code = await agents[2].generate_response(plan, api_key)
118
+ conversation.append({"agent": "Coder", "message": f"Code:\n{code}"})
119
+ log_q.put(f"[Coder]: Code generated:\n{code}")
120
+
121
+ # Step 3: Code Review
122
+ code_review = None
123
+ iteration = 0
124
+ while True:
125
+ if iteration >= 5:
126
+ log_q.put("[Code Reviewer]: Code not approved after 5 iterations: terminating.")
127
+ break
128
+
129
+ code_review = await agents[3].generate_response(code, plan, api_key)
130
+ revised_code = await agents[2].generate_response(
131
+ f"Please revise the code according to the following feedback: {code_review}",
132
+ api_key
133
+ )
134
+ code = revised_code
135
+ iteration += 1
136
+
137
+ if code == revised_code:
138
+ break
139
+
140
+ log_q.put(f"[Code Reviewer]: Feedback received:\n{code_review}")
141
+ log_q.put(f"[Code Reviewer]: Revised code:\n{revised_code}")
142
+
143
+ # Step 4: Documentation
144
+ doc = await agents[4].generate_response(code, api_key)
145
+ conversation.append({"agent": "Documentation Agent", "message": f"Documentation:\n{doc}"})
146
+ log_q.put(f"[Documentation Agent]: Documentation generated:\n{doc}")
147
+
148
+ except Exception as e:
149
+ log_q.put(f"[All Agents]: An error occurred: {str(e)}")
150
+ logger.error(f"Error in multi_agent_conversation: {str(e)}")
151
+
152
+ finally:
153
+ thread.join()
154
+
155
+ async def multi_agent_conversation_wrapper(task_message: str, api_key: str) -> None:
156
+ await multi_agent_conversation(
157
+ task_message,
158
+ log_q=queue.Queue(),
159
+ api_key=api_key,
160
+ additional_inputs=[gr.Textbox(label="OpenAI API Key (optional)") if api_key is None else None]
161
+ )
162
 
163
  if __name__ == "__main__":
164
+ iface = gr.ChatInterface(
165
+ fn=multi_agent_conversation_wrapper,
166
+ additional_inputs=[gr.Textbox(label="OpenAI API Key (optional)")],
167
+ type="messages",
168
+ title="Actual Multi-Agent Conversation Chatbot",
169
+ description="""
170
+ - Collaborative workflow between Prompt Enhancer, Orchestrator, Coder, Code-Reviewer and Documentation Agent agents.
171
+ - Enter a task description to observe the iterative workflow between the agents.
172
+ - NOTE: The kill-switch mechanism will terminate after five code rejection iterations to prevent endless loops.
173
+ - NOTE3: You can input your OPENAI_API_KEY at the bottom of the page for this to work!
174
+ """,
175
+ )
176
+
177
+ iface.launch()