diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bb0ad84 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ + +# mac +.DS_Store + +# python +__pycache__/ +*.pyc + +# IDE +.vscode/ + + + +# Environment +.env diff --git a/core/action.py b/core/action.py new file mode 100644 index 0000000..09556b2 --- /dev/null +++ b/core/action.py @@ -0,0 +1,22 @@ +from pydantic import BaseModel +from typing import Any + + +class Action(BaseModel): + thought:str + action:str + action_input:str + oberservation:str="" + + def to_string(self,keep_thought_prefix=True): + + if keep_thought_prefix: + thought_str="Thought: "+self.thought+"\n" + else: + thought_str=self.thought+"\n" + + return thought_str+ \ + "Action: "+self.action+"\n"+ \ + "Action Input: "+self.action_input+"\n"+ \ + "Observation: "+self.oberservation + diff --git a/core/config.py b/core/config.py index e4736e2..358e614 100644 --- a/core/config.py +++ b/core/config.py @@ -1,9 +1,14 @@ import os +from dotenv import load_dotenv + + class Config: OPENAI_MODEL_BASIC = 'gpt-4o-mini' OPENAI_MODEL_ADVANCED = 'gpt-4o' OPENAI_MODEL_EXPERT = 'o1-preview' - os.environ['OPENAI_API_BASE'] = 'https://api.ohmygpt.com/v1/' - os.environ['OPENAI_API_KEY'] = '' + #os.environ['OPENAI_API_BASE'] = 'https://api.ohmygpt.com/v1/' + #os.environ['OPENAI_API_KEY'] = '' + + load_dotenv() diff --git a/core/context_manager.py b/core/context_manager.py index 3dfddf6..756a6b8 100644 --- a/core/context_manager.py +++ b/core/context_manager.py @@ -2,7 +2,7 @@ class ContextManager: def __init__(self): self.context = {} - + def get_context(self): return self.context @@ -30,4 +30,4 @@ def context_to_str(self): def print_context(self): print(f"Here is the context:") - print(self.contextToString()) \ No newline at end of file + print(self.context_to_str()) \ No newline at end of file diff --git a/core/llm_chat.py b/core/llm_chat.py index 5ca2882..4945a6c 100644 --- a/core/llm_chat.py +++ b/core/llm_chat.py @@ -1,5 +1,6 @@ import json -from typing import Dict, Any +from typing import Dict, Any,List,Union +import re from langchain_core.documents import Document from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder @@ -11,6 +12,7 @@ from langchain.globals import set_debug from langchain.chains.combine_documents import create_stuff_documents_chain from langchain_openai import ChatOpenAI +from langchain_core.messages import HumanMessage from core.config import Config from core.pydantic_validator import PydanticValidator @@ -20,9 +22,10 @@ set_debug(True) + class LLMChat: - def __init__(self, model_type = 'BASIC'): + def __init__(self, model_type = 'BASIC',validator=None): if model_type == 'ADVANCED': model = Config.OPENAI_MODEL_ADVANCED @@ -31,9 +34,18 @@ def __init__(self, model_type = 'BASIC'): else: model = Config.OPENAI_MODEL_BASIC - self.chat = ChatOpenAI(model=model, temperature=0.1, verbose=True) + self.chat = ChatOpenAI(model=model, temperature=0.1,verbose=True) self.chat_history_for_chain = ChatMessageHistory() + self.validator=validator + + def one_time_respond_str(self, prompt): + + output_parser = StrOutputParser() + chain = self.chat | output_parser + response = chain.invoke(prompt) + return response + def one_time_respond(self, request): prompt = ChatPromptTemplate.from_messages( [ @@ -200,7 +212,6 @@ def context_respond_with_tools(self, context_str, tools, messages_str): "messages": messages, } ) - validator = Validator() try: try: answer_data = json.loads(result['answer']) @@ -218,7 +229,7 @@ def context_respond_with_tools(self, context_str, tools, messages_str): input_args = arguments print(f"arg_definition: {arg_definition}") print(f"input_args: {input_args}") - # validator.validate(input_args, arg_definition, validator.ARGUMENTS_NOT_MATCH) #TODO + # self.validator.validate(input_args, arg_definition, validator.ARGUMENTS_NOT_MATCH) #TODO if not self._validate_arguments(input_args,arg_definition): message_str = messages[0] input_args_str = json.dumps(input_args) @@ -272,9 +283,8 @@ def one_time_respond_with_validation(self, request): ], } ) - validator = Validator() try: - validator.validate(request, response, validator.CHECK_UNCERTAINTY) + self.validator.validate(request, response, self.validator.CHECK_UNCERTAINTY) return response except ValueError as error: attempt += 1 diff --git a/core/llm_validator.py b/core/llm_validator.py index 57f2c97..b11d910 100644 --- a/core/llm_validator.py +++ b/core/llm_validator.py @@ -1,13 +1,21 @@ +import json +from typing import List,Union import re -from typing_extensions import Annotated -from pydantic import BaseModel, ValidationError, AfterValidator, ValidationInfo -class LLMValidator: +from core.llm_chat import LLMChat - def create_validator_prompt(self, request, response): - prompt = f""" + +class Validator(object): + + def __init__(self,model_type = 'BASIC',tools=[]): + + self.chat = LLMChat(model_type) + + + def create_validator_prompt(self,request,response): + prompt=f""" You are an expert validator of AI-generated outputs. Evaluate the provided subtask output based on the following criteria: 1. **Accuracy** (Score 1-5): The output fulfills the requirements of the subtask accurately. @@ -24,15 +32,20 @@ def create_validator_prompt(self, request, response): - **Score (1-5)** - **Justification:** A brief explanation for your score. + in following example: + 1. **Accuracy (Score 5)**: The output correctly completed the task. + At the end: - Calculate the **Total Score**. - - Provide a final recommendation: + - Provide a **Final Recommendation:** - **Accept Output** if the total score is above 35 and no criterion scored below 3. - **Rerun Subtask** if the total score is 35 or below, or if any criterion scored below 3. - - If recommending a rerun, provide suggestions on how to improve the output. + - If recommending a rerun, in **Suggestions** provide suggestions on how to improve the output. + + You must follow the output format! Don't add additional symbol in section keys. --- @@ -57,6 +70,7 @@ def parse_validation_response(self, validation_response): else: return "Undetermined" + def parse_scored_validation_response(self, validation_response): scores = [] total_score = 0 @@ -82,18 +96,22 @@ def parse_scored_validation_response(self, validation_response): else: decision = "Rerun Subtask" - return decision, total_score, scores + # Final Recommendation + m=re.search(r"\*\*suggestions\*\*:?",validation_response.lower()) + if m: + suggestions=validation_response[m.start():] + else: + suggestions="" + return decision, total_score, scores,suggestions + def validate(self, request, response): prompt = self.create_validator_prompt(request, response) - from core.llm_chat import LLMChat - chat = LLMChat() - - validation_response = chat.one_time_respond(prompt) + validation_response = self.chat.one_time_respond(prompt) - # validation_response = check_result['choices'][0]['message']['content'] return validation_response + # Example usage if __name__ == "__main__": diff --git a/core/planner.py b/core/planner.py index ca9562c..0752f38 100644 --- a/core/planner.py +++ b/core/planner.py @@ -1,36 +1,86 @@ import json, re -from core.llm_chat import LLMChat from core.step_manager import Step -from prompt.plan import PLAN_FORMAT +from prompt.plan import PLAN_FORMAT,RE_PLAN_FORMAT,LOW_LEVEL_PLANNER_FORMAT,REVIEW_FORMAT +from core.llm_chat import LLMChat + class Planner: - def __init__(self): + def __init__(self,model_type=None): + self.history_steps=[] self.steps = [] + self.chat = LLMChat(model_type) + + def plan_with_template_format(self, request, background="",knowledge=""): + + if knowledge: + knowledge=f"\n{knowledge}\n\n" + + sys_prompt=PLAN_FORMAT.format(background=background,knowledge=knowledge) + + response = self.chat.prompt_respond(request, sys_prompt).replace("```json", '').replace("```", '') + steps_data = json.loads(response)["steps"] + for i,step_data in enumerate(steps_data): + step = Step(i+1,step_data["step_name"], step_data["step_description"]) + self.steps.append(step) + return self.steps[:] + + def replan(self,request, completed_steps:list[Step], background="",knowledge=""): + + + if knowledge: + knowledge=f"\n{knowledge}\n\n" + + original_plan_str="\n".join(str(step) for step in self.steps) + completed_steps_str="\n".join([step.get_context_str() for step in completed_steps]) - def plan(self, request, **kwargs): - plan_content = "" - background = "" - if 'background' in kwargs: - background = kwargs['background'] - plan_content += f"\n{background}\n\n" - knowledge = "" - if 'knowledge' in kwargs: - knowledge = kwargs['knowledge'] - plan_content += f"\n{knowledge}\n\n" - plan_content += PLAN_FORMAT + critiqe_prompt=REVIEW_FORMAT.format(request=request, + original_plan=original_plan_str,completed_steps=completed_steps_str) + critique_response=self.chat.one_time_respond(critiqe_prompt) - chat = LLMChat() + + + prompt=RE_PLAN_FORMAT.format(background=background,knowledge=knowledge,request=request, + original_plan=original_plan_str,completed_steps=completed_steps_str,criteque=critique_response) + + - response = chat.prompt_respond(request, plan_content).replace("```json", '').replace("```", '') + response = self.chat.one_time_respond(prompt).replace("```json", '').replace("```", '') steps_data = json.loads(response)["steps"] + + self.history_steps.append(self.steps) + + remaining_steps=[] + + for i,step_data in enumerate(steps_data): + step = Step(len(completed_steps)+i+1,step_data["step_name"], step_data["step_description"]) + remaining_steps.append(step) + + self.steps=completed_steps+remaining_steps + + return remaining_steps + + + def low_level_plan(self,request, step:Step,tools): + tool_descriptions="\n".join([f"{tool.name}: {tool.description}" for tool in tools]) + prompt=LOW_LEVEL_PLANNER_FORMAT.format(request=request,step=step,tools=tool_descriptions) + + + response = self.chat.one_time_respond(prompt).replace("```json", '').replace("```", '') + steps_data = json.loads(response)["steps"] + + sub_steps=[] + for step_data in steps_data: - step = Step(step_data["step_name"], step_data["step_description"]) - self.steps.append(step) - return self.steps + sub_step = Step(step_data["step_name"], step_data["step_description"]) + sub_steps.append(sub_step) + + step.add_sub_steps(sub_steps) + return step + def extract_steps(plan_string): # Define an empty list to store the steps diff --git a/core/react.py b/core/react.py new file mode 100644 index 0000000..8935407 --- /dev/null +++ b/core/react.py @@ -0,0 +1,130 @@ +import json +from typing import List,Union +import re + +from langchain_openai import ChatOpenAI +from langchain_core.tools import Tool +from langchain_core.prompts import PromptTemplate +from langchain_core.messages import AIMessage + +from core.config import Config +from core.action import Action + +from prompt.system_context import SYSTEM_CONTEXT_WITH_TOOLS2 + +class ReActBot(object): + + def __init__(self,model_type = 'BASIC',tools=[]): + + if model_type == 'ADVANCED': + model = Config.OPENAI_MODEL_ADVANCED + else: + model = Config.OPENAI_MODEL_BASIC + + self.chat = ChatOpenAI(model=model, temperature=0.1, verbose=True) + + self.stop=[ + f"\nObservation:", + f"\n\tObservation:", + ] + self.chat=self.chat.bind(stop=self.stop) + self.tools:list[Tool]=tools + + self.messages=[] + self.intermediate_steps:List[Action]=[] + + self.prompt = PromptTemplate( + template=SYSTEM_CONTEXT_WITH_TOOLS2, + input_variables=["input", "agent_scratchpad","history_message"], + partial_variables={ + "tool_names": ", ".join([tool.name for tool in self.tools]), + "tool_descriptions": "\n".join( + [f"{tool.name}: {tool.description}\n{tool.args}" for tool in self.tools] + ), + }, + ) + + self.agent=self.prompt | self.chat | self.parse_output + + + def invoke(self,query,suggestions=""): + + finished=False + retry=0 + query=query+suggestions + + while retry<3 and (not finished): + try: + agent_scratchpad=self._build_agent_scratchpad() + response=self.agent.invoke({"input":query,"agent_scratchpad":agent_scratchpad,"history_message":"\n".join(self.messages)}) + + if isinstance(response,str): + finished=True + + current_message=f"""Command: {query} + {agent_scratchpad} + Final Answer: {response} + """ + self.messages.append(current_message) + + self.intermediate_steps=[] + elif isinstance(response,Action): + try: + observation=self.execute_action(response) + response.oberservation=observation + self.intermediate_steps.append(response) + except Exception as e: + pass + + else: + retry+=1 + + except Exception as e: + print("Generate failed, retry.",str(e)) + retry+=1 + + if retry>=3: + response="The step cannot be executed correctly." + + return response,current_message + + def execute_action(self,action:Action): + for tool in self.tools: + if action.action==tool.name: + input_dict=json.loads(action.action_input.replace("```json", '').replace("```", '')) + result=tool.invoke(input_dict) + return result + + raise Exception("Not Found tool :{action.action}") + + def _build_agent_scratchpad(self): + outputs=[] + + for step in self.intermediate_steps: + outputs.append(step.to_string()) + + return "\n".join(outputs) + + def parse_output(self,message: AIMessage)-> Union[Action, str]: + text = message.content + + action_regex =r"(?:Thought\s*:)?s*(.*?)\n\s*Action\s*:\s*(.*?)\n\s*Action\s*Input\s*:\s*(.*)" + + final_answer_regex=r"Final\s*Answer\s*:\s*(.*)\n?" + + m_final_answer=re.search(final_answer_regex,text) + m_action=re.search(action_regex,text) + + if m_final_answer and m_action: + if m_final_answer.start()\n{self.name}: {self.response}\n\n" + + return contextStr def __repr__(self): return f"Step(name='{self.name}', description='{self.description}', sub_steps={self.sub_steps})" + + def __str__(self): + return f"Step {self.index} - {self.name}: {self.description}" class StepManager: diff --git a/core/tools.py b/core/tools.py new file mode 100644 index 0000000..ee4ccf9 --- /dev/null +++ b/core/tools.py @@ -0,0 +1,166 @@ +from langchain_core.tools import Tool,tool +from typing import Annotated, List +from core.llm_chat import LLMChat +import subprocess +import time +import os +import select +import signal +import fcntl + + +@tool +def read_code_from_file(file_path:Annotated[str,"file path to save the code"]): + """Read code in given file""" + print("Tool: read_code_from_file") + print("File Path:",file_path) + + try: + with open(file_path,"r",encoding="utf-8") as f: + code=f.read() + except Exception as e: + return f"read code from file failed: {e}" + + return code + + +@tool +def save_code_to_file(code:Annotated[str,"Complete code save to one file"], + file_path:Annotated[str,"file path to save the code"]): + """Create a file with file_path and save the code given in that file""" + print("Tool: save_code_to_file") + print("Code:",code) + print("File Path:",file_path) + + try: + with open(file_path,"w",encoding="utf-8") as f: + f.write(code) + except Exception as e: + return f"save code to file failed: {e}" + + return f"code has been successfully saved to {os.path.abspath(file_path)}" + +def set_nonblocking(fd): + flags = fcntl.fcntl(fd, fcntl.F_GETFL) + fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK) + +@tool +def execute_command_line(shell_command:Annotated[str,"Shell command to be executed in python subprocess"], + is_run_asynchronously:Annotated[bool,"if true, use subprocess.Popen without waiting for it to complete. If no, use subprocess.run, wait for finish and return"]): + """Execute the command by python's subprocess.Popen or subprocess.run based on is_run_asynchronously and get the response. +is_run_asynchronously is usefull when the command should keep running in background. +Note only each execution is in the same session, the session will end after exit""" + print("Tool: execute_command_line") + print("Command:",shell_command) + print("is_run_asynchronously:",is_run_asynchronously) + + + process = subprocess.Popen( + shell_command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + shell=True, + preexec_fn=os.setsid + ) + + if is_run_asynchronously: + set_nonblocking(process.stdout) + set_nonblocking(process.stderr) + + time.sleep(10) # sleep a period before checking error + stdout_ready, stderr_ready, _ = select.select([process.stdout], [process.stderr], [], 0) + + if stderr_ready: + stderr = process.stderr.read(1024) + stdout = process.stdout.read(1024) + return f"stdout: {stdout}\n stderr: {stderr} The PID is: {process.pid}" + else: + response="No immediate errors. Process continues running in the background.The PID is: {process.pid}" + + + else: + try: + # Poll the process for completion and set a timeout + stdout, stderr = process.communicate(timeout=5*60) + response=f"stdout: {stdout}\n stderr: {stderr}" + + except subprocess.TimeoutExpired: + response="The command took too long and was terminated. Should the command run in async?" + os.killpg(os.getpgid(process.pid), signal.SIGTERM) # Send SIGTERM to the process group + + # Kill the process + process.kill() + + # Wait for the process to terminate + process.wait() + + + + + return response + +@tool +def execute_command_line_mock(shell_command:Annotated[list[str],"Shell command to be executed in python subprocess.run"]): + """Execute the command by python's subprocess.run and get the response""" + print("Tool: execute_command_line") + print("Command:",shell_command) + + result="Succeed" + return f"Response of the command: {result}" + + +@tool +def write_code(language:Annotated[str,"the coding language"], + task:Annotated[str,"description of task the code need to achive"]): + """write code with given language to accomplish the task""" + + print("Tool: write_code") + print("language:",language) + print("task:",task) + + llm=LLMChat() + system_prompt=f"""You are a {language} developer. Write code based on user's query. +You must output the code only and use comments to explain the code.""" + response=llm.prompt_respond(task, system_prompt) + + return response + +@tool +def modify_code(language:Annotated[str,"the coding language"], + file_path:Annotated[str,"file path of current code"], + task:Annotated[str,"description of task the code need to achive"]): + """Modify current code with given language to accomplish the task""" + + print("Tool: modify_code") + print("file_path:",file_path) + print("language:",language) + print("task:",task) + + current_code=read_code_from_file(file_path) + + llm=LLMChat() + system_prompt=f"""You are a {language} developer. Modify code based on user's query. +You must output the complete code.Output the code only and use comments to explain the code. Don't include anything else. + + +{current_code} + + +{task} +""" + response=llm.one_time_respond_str( system_prompt) + + return response + +@tool +def human_for_help(action:Annotated[str,"description of action that need human to do manully"]): + """You can ask human for assistant to complete one single action such as: download a file, install a software... + You need to provide the action instruction. Only ask human for assistant when is nessasary.""" + + print("Tool: human_for_help") + print("action:",action) + + response=input("Type the result after perform the action") + + return response \ No newline at end of file diff --git a/prompt/plan.py b/prompt/plan.py index 85f3054..6a6de3d 100644 --- a/prompt/plan.py +++ b/prompt/plan.py @@ -1,4 +1,165 @@ -PLAN_FORMAT = """ -Based on background and knowledge given above to generate a plan, put each step in the json format as the output, step has attribute step_name and step_description, all steps are under 'steps' root attribute. +# one level planner + +PLAN_FORMAT = """{background} +Based on the request given below to generate a plan for AI assistant to execute automatically. This plan should involve individual steps, that if executed correctly will yield the correct answer. +Do not create any superfluous steps. Do not contain manual steps required to execute by human. +You can refer to the knowledge to generate the plan if given. +The result of the final step should be the final answer. Make sure that each step has all the information needed - do not skip steps. +Each step will be executed in order independently, note that in memory result will not be kept to next step! +Put each step in the json format as the output, step has attribute step_name and step_description, all steps should form a list under 'steps' root attribute. ex: step_name: Prepare eggs, step_description: Get the eggs from the fridge and put on the table. -""" \ No newline at end of file + +{knowledge} + +""" + +REVIEW_FORMAT="""You task is to give critiques for current plan given an objective. +Given the objective and response of completed steps, review the original plan and provide criteques for following two points only. Don't provide revised steps! +1) what is different between the last completed step and the original plan +2) suggestion about how to change the REMAINING plan based on completed steps.Ensure the steps should be able to execute by AI assistant not human. +If you think no remaining step required, just say so. + + + +{request} + + + + + +{original_plan} + + + + + +{completed_steps} + + + +""" + +RE_PLAN_FORMAT = """{background} +Based on the infor given below to generate a plan for computer to execute automatically. This plan should involve individual steps, that if executed correctly will yield the correct answer. +Do not create any superfluous steps. Do not contain manual steps required to execute by human. +You can refer to the knowledge to generate the plan if given. +The result of the final step should be the final answer. Make sure that each step has all the information needed - do not skip steps. +Each step will be executed in order independently, note that in memory result will not be kept to next step! +Put each step in the json format as the output, step has attribute step_name and step_description, all steps should form a list under 'steps' root attribute. +ex: step_name: Prepare eggs, step_description: Get the eggs from the fridge and put on the table. + +{knowledge} + + + +{request} + + + + + +{original_plan} + + + + + +{completed_steps} + + + + + +{criteque} + + + +Revise the remaining steps of your previous plan using the new information. +You should use the critique, response of completed_steps, review the original plan and update your steps accordingly. If no more steps are needed and you can return to the user, then respond with that. Otherwise, fill out the steps. Only add steps to the plan that still NEED to be done. Do not return previously done steps as part of the plan. +Only return the steps in json format, don't add anything else! +""" + + + + + +# Two level planner + +HIGHER_LEVEL_PLANNER_FORMAT = """ +Based on background and knowledge given above to generate a plan for AI assistant. This plan should involve individual tasks, that if executed correctly will yield the correct answer. +The result of the final step should be the final answer. Make sure that each step has all the information needed - do not skip steps. +Put each step in the json format as the output, step has attribute step_name and step_description, all steps should form a list under 'steps' root attribute. +ex: step_name: Prepare eggs, step_description: Get the eggs from the fridge and put on the table. +""" + +LOW_LEVEL_PLANNER_FORMAT=""" +Provide the final objective and current step. Break down the current step to individual tasks, that if executed correctly will yield the correct answer for the step. Do not create any superfluous tasks. +Only breakdown the current step, don't add other tasks beyond the scope of given step. +You can refer to the tools available to break down the step. +The result of the final step should be the final answer. Make sure that each step has all the information needed - do not skip steps. +Put each step in the json format as the output, step has attribute step_name and step_description, all steps should form a list under 'steps' root attribute. + + + +{request} + + + + + +{step} + + + + + +{tools} + + + +""" + + + + + + +LOW_LEVEL_RE_PLAN_FORMAT = """ +Provide the final objective and current step. Break down the current step to individual tasks, that if executed correctly will yield the correct answer for the step. Do not create any superfluous tasks. +Only breakdown the current step, don't add other tasks beyond the scope of given step. +You can refer to the tools available to break down the step. +The result of the final step should be the final answer. Make sure that each step has all the information needed - do not skip steps. +Put each step in the json format as the output, step has attribute step_name and step_description, all steps should form a list under 'steps' root attribute. + + + +{request} + + + + + +{step} + + + + + +{tools} + + + + + +{original_tasks} + + + + + +{completed_tasks} + + + +Update your tasks accordingly. If no more tasks are needed and you can return to the user, then respond with that. Otherwise, fill out the tasks. Only add steps to the plan that still NEED to be done. Do not return previously done steps as part of the plan. +""" diff --git a/prompt/system_context.py b/prompt/system_context.py index 8e22be4..33a5f96 100644 --- a/prompt/system_context.py +++ b/prompt/system_context.py @@ -30,4 +30,39 @@ If no tools is relevant to use, don't make something up and just say "I don't know how to handle this request, it may need to breakdown.". -""" \ No newline at end of file +""" + + +SYSTEM_CONTEXT_WITH_TOOLS2= """ + +You are a helpful assistant to perform task based on the command. +Based on command, you should: +1) evaluate whether the user query can be solved by tools provided below. If no, say why. If command is not clear, ask for clarification. +2) ONLY do things described in the command. You don't need to validate the observation. +3) You must summary intermediate actions and detail the final output in final answer. +4) Start the Thought, Action, Action Input, Observation loop to execute the plan + +You should only use tools documented below. +Some user queries can be resolved in a single tool call, but some will require several tool calls. + +Here are tools you can use: +{tool_descriptions} + +Starting below, you should follow this format: + +Command: the command you need to execute +Thought: you should always think about what to do +Action: the action to take, should be one of the tools [{tool_names}] +Action Input: the input to the action. Should be in valid json format +Observation: the output of the action +... (this Thought/Action/Action Input/Observation can repeat N times) +Thought: I am finished executing the plan (or, I cannot finish executing the plan without knowing some other information.) +Final Answer: the final output from executing the plan(summary intermediate actions and detail the final output) or the step can not be excuted need re-plan. Output in one line. + +Begin! + +{history_message} +Command: {input} +Thought:{agent_scratchpad} +""" + diff --git a/tool_integration_experiment.py b/tool_integration_experiment.py new file mode 100644 index 0000000..4f068da --- /dev/null +++ b/tool_integration_experiment.py @@ -0,0 +1,97 @@ +import re + +from core.llm_chat import LLMChat +from core.react import ReActBot +from core.planner import Planner +from core.step_manager import Step +from core.llm_validator import Validator +import core.tools as T + +def run(): + print("Build web application...") + + background = """You are a full stack developer, always deliver best quality application. You need to make sure devlivered application meet request. + Develop a new the application or modify existing application based on user's request. + Create a new folder if it is a new project. Only start up service in the end of development.""" + + knowledge="" + + #request = "Build a react web application, click button will popup hello world on the screen." + + request = """Use python to build a web application, click button will popup current time on the screen. The current time should be obtained from backend python service. """ + + # request = """Modify the current code in time_popup_app and make sure the application satisfy the following requirement: + # use python to build a web application, click button will popup current time on the screen. The current time should be obtained from backend python service. """ + + #request = """modify code in app.py and static. add an input box on the page to accept user's name. after click button, the popup should display username and current time""" + + + planner = Planner(model_type = 'ADVANCED') + + validator=Validator(model_type = 'ADVANCED') + + tools=[T.save_code_to_file,T.read_code_from_file,T.execute_command_line,T.write_code,T.modify_code] + react_chat=ReActBot(tools=tools,model_type = 'ADVANCED') + + index=1 + + completed_steps=[] + + steps:list[Step] = planner.plan_with_template_format(request, background=background,knowledge=knowledge) + + while len(steps)>0: + + is_rerun=True + suggestions="" + + step=steps.pop(0) + + while is_rerun: + # TODO: rerun with reversed steps + step_response, whole_process= react_chat.invoke(query=step.description,suggestions=suggestions) + + validator_response=validator.validate(step.description,step_response) + decision, total_score, scores,suggestions = validator.parse_scored_validation_response(validator_response) + print(validator_response) + print("Total Score:", total_score) + print("Scores by Criterion:", scores) + print("Final Decision:", decision) + print("Suggestion:", suggestions) + + if decision=="Accept Output": + is_rerun=False + suggestions="" + + + step.add_response(step_response) + + completed_steps.append(step) + + steps=planner.replan(request, completed_steps,background=background) + + + + # step_with_substeps=planner.low_level_plan(request, step,tools) + + # sub_steps=step_with_substeps.sub_steps + + # context_manager = ContextManager() + + # sub_index=1 + # while len(sub_steps)>0: + # sub_step=sub_steps[0] + # context_response = react_chat.invoke(query=sub_step.description) + # step_number = f"Step {index}.{sub_index}" + # context = f"{sub_step.description}: \n{context_response}" + # context_manager.add_context(step_number, context) + + # original_tasks="\n".join(str(step) for step in sub_steps) + # sub_steps=planner.replan(request, step_with_substeps,tools,context_manager.context_to_str(),original_tasks,completed_tasks=context_manager.context_to_str()) + + # sub_index+=1 + + index +=1 + + +if __name__ == "__main__": + run() \ No newline at end of file