KieranXu/path_advisor
0
1import os2import sys3import threading4from itertools import chain5 6import anyio7from flaml import autogen8import gradio as gr9from autogen import Agent, AssistantAgent, OpenAIWrapper, UserProxyAgent, ConversableAgent10from autogen.code_utils import extract_code11from gradio import ChatInterface, Request12from gradio.helpers import special_args13 14LOG_LEVEL = "INFO"15TIMEOUT = 6016 17 18class myChatInterface(ChatInterface):19 async def _submit_fn(20 self,21 message: str,22 history_with_input: list[list[str | None]],23 request: Request,24 *args,25 ) -> tuple[list[list[str | None]], list[list[str | None]]]:26 history = history_with_input[:-1]27 inputs, _, _ = special_args(self.fn, inputs=[message, history, *args], request=request)28 29 if self.is_async:30 await self.fn(*inputs)31 else:32 await anyio.to_thread.run_sync(self.fn, *inputs, limiter=self.limiter)33 34 # history.append([message, response])35 return history, history36 37 38with gr.Blocks() as demo:39 40 def flatten_chain(list_of_lists):41 return list(chain.from_iterable(list_of_lists))42 43 class thread_with_trace(threading.Thread):44 # https://www.geeksforgeeks.org/python-different-ways-to-kill-a-thread/45 # https://stackoverflow.com/questions/6893968/how-to-get-the-return-value-from-a-thread46 def __init__(self, *args, **keywords):47 threading.Thread.__init__(self, *args, **keywords)48 self.killed = False49 self._return = None50 51 def start(self):52 self.__run_backup = self.run53 self.run = self.__run54 threading.Thread.start(self)55 56 def __run(self):57 sys.settrace(self.globaltrace)58 self.__run_backup()59 self.run = self.__run_backup60 61 def run(self):62 if self._target is not None:63 self._return = self._target(*self._args, **self._kwargs)64 65 def globaltrace(self, frame, event, arg):66 if event == "call":67 return self.localtrace68 else:69 return None70 71 def localtrace(self, frame, event, arg):72 if self.killed:73 if event == "line":74 raise SystemExit()75 return self.localtrace76 77 def kill(self):78 self.killed = True79 80 def join(self, timeout=0):81 threading.Thread.join(self, timeout)82 return self._return83 84 def update_agent_history(recipient, messages, sender, config):85 if config is None:86 config = recipient87 if messages is None:88 messages = recipient._oai_messages[sender]89 message = messages[-1]90 message.get("content", "")91 # config.append(msg) if msg is not None else None # config can be agent_history92 return False, None # required to ensure the agent communication flow continues93 94 def _is_termination_msg(message):95 """Check if a message is a termination message.96 Terminate when no code block is detected. Currently only detect python code blocks.97 """98 if isinstance(message, dict):99 message = message.get("content")100 if message is None:101 return False102 cb = extract_code(message)103 contain_code = False104 for c in cb:105 # todo: support more languages106 if c[0] == "python":107 contain_code = True108 break109 return not contain_code110 111 def initialize_agents(config_list):112 113 assistant = AssistantAgent(114 name="assistant",115 max_consecutive_auto_reply=5,116 llm_config={117 # "seed": 42,118 "timeout": TIMEOUT,119 "config_list": config_list,120 },121 )122 123 angent_survey = ConversableAgent(124 name = "agent_survey",125 system_message="you are dedicated Learning Path Advisor. "126 "the first task is understanding users goal and educational background, interests, career aspirations and other necessary information. "127 "Interact with the user until you have sufficient information, or the user offers a message ending with 'Exit'."128 "Please gently guide the user,ask questions one by one."129 "based on the critic feedback, if needed, further ask user."130 "summarizing the user's information but do not do recommandations.",131 llm_config={"config_list": config_list},132 is_termination_msg=lambda msg: "EXIT" in msg["content"], # terminate 133 human_input_mode="NEVER", # never ask for human input134 )135 angent_recommander =ConversableAgent(136 name = "angent_recommander",137 system_message="you are dedicated Learning Path Advisor."138 "based on the user's information from 'agent_survey', critic and user, plan a learning path"139 "your task is to help user navigate through the vast ocean of courses and specializations, finding the perfect path that aligns with user's background."140 "the learning path should include necessary information of course such as course title, providers and thoughtful reasons for the recommendation"141 "Then, ask the critic's opinion. and try to improve based on the opinion of critics"142 "Rule 1. The total number of courses should be less than 4",143 llm_config={"config_list": config_list},144 is_termination_msg=lambda msg: "EXIT" in msg["content"], 145 human_input_mode="NEVER", # never ask for human input146 )147 148 149 userproxy = UserProxyAgent(150 name="userproxy",151 system_message ="a human user",152 human_input_mode="NEVER",153 is_termination_msg=_is_termination_msg,154 max_consecutive_auto_reply=5,155 # code_execution_config=False,156 code_execution_config={157 #"last_n_messages": 2,158 "work_dir": "path_advisor",159 "use_docker": False, # set to True or image name like "python:3" to use docker160 },161 )162 163 critic = AssistantAgent(164 name="Critic",165 system_message="Critic. Double check leanring path, reasons, from other agents and provide feedback. you should Reflect at least these questions"166 "Q1: Whether the recommended course meets the user's interests or objective?"167 "Q2: Do learning paths lead to higher motivation, or could they possibly lead to an overload of choices that paralyze some learners?"168 "Q3: Is the content provided in-depth enough to foster a comprehensive understanding?"169 "Q4: Is there a logical progression in the curriculum that builds on previous knowledge?"170 "if you think the plan should imporved further, give the feedback to 'angent_recommander' and ask it to improve the learning path"171 "if you think the plan is good enough, then ask the user if he would like to try the learning path?",172 llm_config={"config_list": config_list},173 )174 175 Learning_Path_summary = ConversableAgent(176 name = "Learning_Path_summary",177 system_message="You only followed by an approved leanring path plan by user." 178 "Act as helpful and kind Learning Path Advisor, summarize the previous approved learning path for user, including course title, providers and thoughtful reasons for the recommendation"179 "The tone should be informative, friendly, and supportive."180 "And highlight the keypoints or keywords in orange"181 "Your task is to provide a detailed summary of an approved leanring path plan to user. This overview is designed to give user clarity on the structure, objectives, and resources. the key elements are below:"182 "Learning Path Overview: Goal Alignment; What were the initial goals set at the start of this learning path? How do these objectives align with user's current background or personal development needs?"183 "Curriculum Structure: provide a breakdown of the main topics and learning stages included in this path. What are the key outcomes expected at each stage, and how do they contribute to the overall goal?"184 "Certification and Completion: Upon completing the learning path, what certificates or qualifications will be awarded? How do these credentials support further professional advancement or learning?"185 "Future Learning Opportunities: What subsequent learning opportunities or advanced topics are recommended after completing this path?Are there any additional skills or areas of knowledge that suggest exploring to enhance professional growth?"186 "Conclusion and give encouragement"187 ,188 llm_config={"config_list": config_list},189 is_termination_msg=lambda msg: "EXIT" in msg["content"], 190 human_input_mode="NEVER", # never ask for human input191 ) 192 #group chat with critic193 groupchat = autogen.GroupChat(agents=[userproxy, angent_survey, angent_recommander,critic,Learning_Path_summary], messages=[], max_round=20)194 manager = autogen.GroupChatManager(groupchat=groupchat, llm_config={"config_list": config_list})195 196 # assistant.register_reply([Agent, None], update_agent_history)197 # userproxy.register_reply([Agent, None], update_agent_history)198 199 return userproxy, angent_survey, angent_recommander,critic,Learning_Path_summary, manager200 201 def chat_to_oai_message(chat_history):202 """Convert chat history to OpenAI message format."""203 messages = []204 if LOG_LEVEL == "DEBUG":205 print(f"chat_to_oai_message: {chat_history}")206 for msg in chat_history:207 messages.append(208 {209 "content": msg[0].split()[0] if msg[0].startswith("exitcode") else msg[0],210 "role": "user",211 }212 )213 messages.append({"content": msg[1], "role": "assistant"})214 return messages215 216 def oai_message_to_chat(oai_messages, sender):217 """Convert OpenAI message format to chat history."""218 chat_history = []219 messages = oai_messages[sender]220 if LOG_LEVEL == "DEBUG":221 print(f"oai_message_to_chat: {messages}")222 for i in range(0, len(messages), 2):223 chat_history.append(224 [225 messages[i]["content"],226 messages[i + 1]["content"] if i + 1 < len(messages) else "",227 ]228 )229 return chat_history230 231 def agent_history_to_chat(agent_history):232 """Convert agent history to chat history."""233 chat_history = []234 for i in range(0, len(agent_history), 2):235 chat_history.append(236 [237 agent_history[i],238 agent_history[i + 1] if i + 1 < len(agent_history) else None,239 ]240 )241 return chat_history242 243 def initiate_chat(config_list, user_message, chat_history):244 if LOG_LEVEL == "DEBUG":245 print(f"chat_history_init: {chat_history}")246 # agent_history = flatten_chain(chat_history)247 if len(config_list[0].get("api_key", "")) < 2:248 chat_history.append(249 [250 user_message,251 "Hi, nice to meet you!",252 ]253 )254 return chat_history255 else:256 llm_config = {257 # "seed": 42,258 "timeout": TIMEOUT,259 "config_list": config_list,260 }261 manager.llm_config.update(llm_config)262 manager.client = OpenAIWrapper(**manager.llm_config)263 264 manager.reset()265 oai_messages = chat_to_oai_message(chat_history)266 manager._oai_system_message_origin = manager._oai_system_message.copy()267 manager._oai_system_message += oai_messages268 269 try:270 userproxy.initiate_chat(manager, message=user_message)271 messages = userproxy.chat_messages272 chat_history += oai_message_to_chat(messages, manager)273 # agent_history = flatten_chain(chat_history)274 except Exception as e:275 # agent_history += [user_message, str(e)]276 # chat_history[:] = agent_history_to_chat(agent_history)277 chat_history.append([user_message, str(e)])278 279 manager._oai_system_message = manager._oai_system_message_origin.copy()280 if LOG_LEVEL == "DEBUG":281 print(f"chat_history: {chat_history}")282 # print(f"agent_history: {agent_history}")283 return chat_history284 285 def chatbot_reply_thread(input_text, chat_history, config_list):286 """Chat with the agent through terminal."""287 thread = thread_with_trace(target=initiate_chat, args=(config_list, input_text, chat_history))288 thread.start()289 try:290 messages = thread.join(timeout=TIMEOUT)291 if thread.is_alive():292 thread.kill()293 thread.join()294 messages = [295 input_text,296 "Timeout Error: Please check your API keys and try again later.",297 ]298 except Exception as e:299 messages = [300 [301 input_text,302 str(e) if len(str(e)) > 0 else "Invalid Request to OpenAI, please check your API keys.",303 ]304 ]305 return messages306 307 def chatbot_reply_plain(input_text, chat_history, config_list):308 """Chat with the agent through terminal."""309 try:310 messages = initiate_chat(config_list, input_text, chat_history)311 except Exception as e:312 messages = [313 [314 input_text,315 str(e) if len(str(e)) > 0 else "Invalid Request to OpenAI, please check your API keys.",316 ]317 ]318 return messages319 320 def chatbot_reply(input_text, chat_history, config_list):321 """Chat with the agent through terminal."""322 return chatbot_reply_thread(input_text, chat_history, config_list)323 324 def get_description_text():325 return """326 # Hello! ๐ My name is <span style="color:orange;">PathFinder</span>, 327 ## your dedicated Learning Path Advisor here.328 329 Welcome aboard!330 331 I am here to help you navigate through the vast ocean of courses and specializations, finding the perfect path that aligns with your career goals and educational interests.332 333 Whether you are looking to advance in your current field, pivot to a new industry, or simply explore new areas of knowledge, I'm here to guide you every step of the way!334 """335 336 def update_config():337 config_list = autogen.config_list_from_models(338 model_list=[os.environ.get("MODEL", "gpt-4")],339 )340 if not config_list:341 342 selected_model = "gpt-4"343 selected_key = "sk-ifbaI7viN2UnK634A92a07A9679046A392B907Df26AeCf8d"344 selected_url = "https://aihubmix.com/v1"345 346 config_list = [347 {348 "api_key": selected_key,349 "base_url": selected_url,350 #"api_type": "azure",351 #"api_version": "2023-07-01-preview",352 "model": selected_model,353 }354 ]355 356 return config_list357 358 def set_params(model, oai_key, aoai_key, aoai_base):359 os.environ["MODEL"] = model360 os.environ["OPENAI_API_KEY"] = oai_key361 os.environ["AZURE_OPENAI_API_KEY"] = aoai_key362 os.environ["AZURE_OPENAI_API_BASE"] = aoai_base363 364 def respond(message, chat_history, model, oai_key, aoai_key, aoai_base):365 set_params(model, oai_key, aoai_key, aoai_base)366 config_list = update_config()367 chat_history[:] = chatbot_reply(message, chat_history, config_list)368 if LOG_LEVEL == "DEBUG":369 print(f"return chat_history: {chat_history}")370 return ""371 372 config_list= update_config()373 374 userproxy, angent_survey, angent_recommander,critic,Learning_Path_summary, manager = initialize_agents(config_list)375 376 description = gr.Markdown(get_description_text())377 378 with gr.Row() as params:379 txt_model = gr.Dropdown(380 label="Model",381 choices=[382 "gpt-4",383 "gpt-3.5-turbo",384 ],385 allow_custom_value=True,386 value="gpt-4",387 container=True,388 )389 txt_oai_key = gr.Textbox(390 label="OpenAI API Key",391 placeholder="Enter OpenAI API Key",392 max_lines=1,393 show_label=True,394 container=True,395 type="password",396 )397 txt_aoai_key = gr.Textbox(398 label="Azure OpenAI API Key",399 placeholder="Enter Azure OpenAI API Key",400 max_lines=1,401 show_label=True,402 container=True,403 type="password",404 )405 txt_aoai_base_url = gr.Textbox(406 label="Base url",407 placeholder="Enter Base Url",408 max_lines=1,409 show_label=True,410 container=True,411 type="password",412 )413 414 chatbot = gr.Chatbot(415 [],416 elem_id="chatbot",417 bubble_full_width=False,418 avatar_images=(419 "user.png",420 (os.path.join(os.path.dirname(__file__), "advisor.png")),421 ),422 render=False,423 height=600,424 )425 426 txt_input = gr.Textbox(427 scale=4,428 show_label=False,429 placeholder="Enter text and press enter",430 container=False,431 render=False,432 autofocus=True,433 )434 435 chatiface = myChatInterface(436 respond,437 chatbot=chatbot,438 textbox=txt_input,439 additional_inputs=[440 txt_model,441 txt_oai_key,442 txt_aoai_key,443 txt_aoai_base_url,444 ],445 examples=[446 [" I am interested in data science but do not know where to start."],447 [" I'm a software developer and I want to learn about artificial intelligence. I have some experience with Python."],448 [" I have a background in literature and I'm interested in exploring more about the philosophical aspects of humanity. I would like to understand how philosophical theories have influenced human behavior and society."],449 ],450 )451 452 453if __name__ == "__main__":454 demo.launch(share=True, server_name="0.0.0.0")455 