Tachi67 commited on
Commit
4462cc9
·
1 Parent(s): b466b7e

Upload 8 files

Browse files
PlanWriterAskUserFlow.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flow_modules.aiflows.HumanStandardInputFlowModule import HumanStandardInputFlow
2
+
3
+ from typing import Dict, Any
4
+
5
+ from flows.messages import UpdateMessage_Generic
6
+
7
+ from flows.utils import logging
8
+
9
+ log = logging.get_logger(f"flows.{__name__}")
10
+
11
+
12
+ class PlanWriterAskUserFlow(HumanStandardInputFlow):
13
+ def run(self,
14
+ input_data: Dict[str, Any]) -> Dict[str, Any]:
15
+
16
+ query_message = self._get_message(self.query_message_prompt_template, input_data)
17
+ state_update_message = UpdateMessage_Generic(
18
+ created_by=self.flow_config['name'],
19
+ updated_flow=self.flow_config["name"],
20
+ data={"query_message": query_message},
21
+ )
22
+ self._log_message(state_update_message)
23
+
24
+ log.info(query_message)
25
+ human_input = self._read_input()
26
+
27
+ response = {}
28
+ response["feedback"] = human_input
29
+ response["plan"] = "no plan was written"
30
+
31
+ return response
PlanWriterAskUserFlow.yaml ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ request_multi_line_input_flag: False
2
+ end_of_input_string: EOI
3
+
4
+ query_message_prompt_template:
5
+ template: |2-
6
+ {{question}}
7
+ input_variables:
8
+ - "question"
PlanWriterCtrlFlow.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ from copy import deepcopy
3
+ from typing import Any, Dict, List
4
+
5
+ from flow_modules.aiflows.ChatFlowModule import ChatAtomicFlow
6
+
7
+ from dataclasses import dataclass
8
+
9
+
10
+ @dataclass
11
+ class Command:
12
+ name: str
13
+ description: str
14
+ input_args: List[str]
15
+
16
+ class PlanWriterCtrlFlow(ChatAtomicFlow):
17
+ def __init__(
18
+ self,
19
+ commands: List[Command],
20
+ **kwargs):
21
+ super().__init__(**kwargs)
22
+ self.system_message_prompt_template = self.system_message_prompt_template.partial(
23
+ commands=self._build_commands_manual(commands),
24
+ )
25
+ self.hint_for_model = """
26
+ Make sure your response is in the following format:
27
+ Response Format:
28
+ {
29
+ "command": "call plan writer, or to finish with a summary",
30
+ "command_args": {
31
+ "arg name": "value"
32
+ }
33
+ }
34
+ """
35
+
36
+ @staticmethod
37
+ def _build_commands_manual(commands: List[Command]) -> str:
38
+ ret = ""
39
+ for i, command in enumerate(commands):
40
+ command_input_json_schema = json.dumps(
41
+ {input_arg: f"YOUR_{input_arg.upper()}" for input_arg in command.input_args})
42
+ ret += f"{i + 1}. {command.name}: {command.description} Input arguments (given in the JSON schema): {command_input_json_schema}\n"
43
+ return ret
44
+
45
+ @classmethod
46
+ def instantiate_from_config(cls, config):
47
+ flow_config = deepcopy(config)
48
+
49
+ kwargs = {"flow_config": flow_config}
50
+
51
+ # ~~~ Set up prompts ~~~
52
+ kwargs.update(cls._set_up_prompts(flow_config))
53
+
54
+ # ~~~Set up backend ~~~
55
+ kwargs.update(cls._set_up_backend(flow_config))
56
+
57
+ # ~~~ Set up commands ~~~
58
+ commands = flow_config["commands"]
59
+ commands = [
60
+ Command(name, command_conf["description"], command_conf["input_args"]) for name, command_conf in
61
+ commands.items()
62
+ ]
63
+ kwargs.update({"commands": commands})
64
+
65
+ # ~~~ Instantiate flow ~~~
66
+ return cls(**kwargs)
67
+
68
+ def _update_prompts_and_input(self, input_data: Dict[str, Any]):
69
+ if 'goal' in input_data:
70
+ input_data['goal'] += self.hint_for_model
71
+ if 'feedback' in input_data:
72
+ input_data['feedback'] += self.hint_for_model
73
+
74
+ def run(self, input_data: Dict[str, Any]) -> Dict[str, Any]:
75
+ self._update_prompts_and_input(input_data)
76
+ api_output = super().run(input_data)["api_output"].strip()
77
+ try:
78
+ response = json.loads(api_output)
79
+ return response
80
+ except json.decoder.JSONDecodeError:
81
+ new_input_data = input_data.copy()
82
+ new_input_data['feedback'] = "The previous respond cannot be parsed with json.loads. Make sure your next response is in JSON format."
83
+ new_api_output = super().run(new_input_data)["api_output"].strip()
84
+ return json.loads(new_api_output)
PlanWriterCtrlFlow.yaml ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: "PlanWriterControllerFlow"
2
+ description: "Proposes the next action to take towards achieving the goal, and prepares the input for the branching flow"
3
+ enable_cache: True
4
+
5
+ #######################################################
6
+ # Input keys
7
+ #######################################################
8
+
9
+ input_interface_non_initialized: # initial input keys
10
+ - "goal"
11
+
12
+ input_interface_initialized:
13
+ - "plan"
14
+ - "feedback"
15
+
16
+ #######################################################
17
+ # Output keys
18
+ #######################################################
19
+
20
+ output_interface:
21
+ - 'command'
22
+ - 'command_args'
23
+
24
+ backend:
25
+ api_infos: ???
26
+ model_name:
27
+ openai: gpt-4
28
+ azure: azure/gpt-4
29
+
30
+ commands:
31
+ write_plan:
32
+ description: "Write plan to finish the goal with user interaction"
33
+ input_args: ["goal"]
34
+ finish:
35
+ description: "Signal that the objective has been satisfied, return the summary of what was done"
36
+ input_args: ["summary"]
37
+ manual_finish:
38
+ description: "The user demands to quit and terminate the current process"
39
+ input_args: []
40
+ ask_user:
41
+ description: "Ask user a question for confirmation or assistance"
42
+ input_args: ["question"]
43
+
44
+ system_message_prompt_template:
45
+ _target_: langchain.PromptTemplate
46
+ template: |2-
47
+ You are in charge of a department of writing plans to solve a certain goal. You work with a planner, who does all the planning job.
48
+
49
+ Your **ONLY** task is to take the user's goal for you, to decide whether to call the planner to write or re-write the plan, or to finish the current task.
50
+
51
+ When you need to call the plan writer, call the `write_plan` command with the goal specified.
52
+ When the plan is written and the user is satisfied, call the `finish` command to terminate the current process with a summary of what was done in one sentence.
53
+ Whenever you are in doubt, or need to confirm something to the user, call `ask_user` with the question.
54
+
55
+ You **must not** write plans yourself. You only decide whether to call the planner with specified goals or to finish.
56
+
57
+ Your workflow:
58
+ 0. Whenever the user demands to quit or terminate the current process, call `manual_finish` command.
59
+ 1. Upon user request, call the `write_plan` with the goal given.
60
+ 2. The planner will write the plan. The user will examine the plan, and provide feedback.
61
+ 3. Depending on the feedback of the user:
62
+ 3.1. The user provides feedback on how to change the plan, **call the planner with user's specific requirements again, to ask the planner to refine the plan**. Go back to step 2.
63
+ 3.2. The user does not provide details about refining the plan, for example, just stating the fact that the user has updated the plan, **this means the user is satisfied with the plan written, call the `finish` command.**
64
+ 3.3. The user is satisfied with the plan, **call the `finish` command with a summary of what was done**
65
+
66
+ If you have completed all your tasks, make sure to use the "finish" command, with a summary of what was done.
67
+
68
+ Constraints:
69
+ 1. Exclusively use the commands listed in double quotes e.g. "command name"
70
+
71
+ Your response **MUST** be in the following format:
72
+ Response Format:
73
+ {
74
+ "command": "call plan writer, or to finish",
75
+ "command_args": {
76
+ "arg name": "value"
77
+ }
78
+ }
79
+ Ensure your responses can be parsed by Python json.loads
80
+
81
+
82
+ Available Functions:
83
+ {{commands}}
84
+ input_variables: ["commands"]
85
+ template_format: jinja2
86
+
87
+ human_message_prompt_template:
88
+ _target_: flows.prompt_template.JinjaPrompt
89
+ template: |2-
90
+ Here is the plan written by the planner, it might have been updated by the user, depending on the user's feedback:
91
+ {{plan}}
92
+ Here is the feedback from the user:
93
+ {{feedback}}
94
+ input_variables:
95
+ - "plan"
96
+ - "feedback"
97
+ template_format: jinja2
98
+
99
+ init_human_message_prompt_template:
100
+ _target_: flows.prompt_template.JinjaPrompt
101
+ template: |2-
102
+ Here is the goal you need to achieve:
103
+ {{goal}}
104
+ input_variables:
105
+ - "goal"
106
+ template_format: jinja2
PlanWriterFlow.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict, Any
2
+ import os
3
+
4
+ from flow_modules.Tachi67.ContentWriterFlowModule import ContentWriterFlow
5
+ from flows.base_flows import CircularFlow
6
+
7
+
8
+ class PlanWriterFlow(ContentWriterFlow):
9
+ def _on_reach_max_round(self):
10
+ self._state_update_dict({
11
+ "plan": "The maximum amount of rounds was reached before the model generated the plan.",
12
+ "status": "unfinished"
13
+ })
14
+
15
+ @CircularFlow.output_msg_payload_processor
16
+ def detect_finish_or_continue(self, output_payload: Dict[str, Any], src_flow) -> Dict[str, Any]:
17
+ command = output_payload["command"]
18
+ if command == "finish":
19
+ # ~~~ fetch temp file location, plan content, memory file (of upper level flow e.g. ExtLib) from flow state
20
+ keys_to_fetch_from_state = ["temp_plan_file_location", "plan", "plan_file_location"]
21
+ fetched_state = self._fetch_state_attributes_by_keys(keys=keys_to_fetch_from_state)
22
+ temp_plan_file_location = fetched_state["temp_plan_file_location"]
23
+ plan_content = fetched_state["plan"]
24
+ plan_file_location = fetched_state["plan_file_location"]
25
+
26
+ # ~~~ delete the temp plan file ~~~
27
+ if os.path.exists(temp_plan_file_location):
28
+ os.remove(temp_plan_file_location)
29
+
30
+ # ~~~ write plan content to plan file ~~~
31
+ with open(plan_file_location, 'w') as file:
32
+ file.write(plan_content)
33
+
34
+ # ~~~ return the plan content ~~~
35
+ return {
36
+ "EARLY_EXIT": True,
37
+ "plan": plan_content,
38
+ "summary": "PlanWriter: " + output_payload["command_args"]["summary"],
39
+ "status": "finished"
40
+ }
41
+ elif command == "manual_finish":
42
+ # ~~~ delete the temp plan file ~~~
43
+ keys_to_fetch_from_state = ["temp_plan_file_location"]
44
+ fetched_state = self._fetch_state_attributes_by_keys(keys=keys_to_fetch_from_state)
45
+ temp_plan_file_location = fetched_state["temp_plan_file_location"]
46
+ if os.path.exists(temp_plan_file_location):
47
+ os.remove(temp_plan_file_location)
48
+ # ~~~ return the manual quit status ~~~
49
+ return {
50
+ "EARLY_EXIT": True,
51
+ "plan": "no plan was generated",
52
+ "summary": "PlanWriter: PlanWriter was terminated explicitly by the user, process is unfinished",
53
+ "status": "unfinished"
54
+ }
55
+ else:
56
+ return output_payload
PlanWriterFlow.yaml ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: "PlanWriter"
2
+ description: "Generates plan with interactions with the user"
3
+
4
+ _target_: Tachi67.PlanWriterFlowModule.PlanWriterFlow.instantiate_from_default_config
5
+
6
+ input_interface:
7
+ - "goal"
8
+ - "plan_file_location"
9
+
10
+ output_interface:
11
+ - "plan"
12
+ - "status"
13
+ - "summary"
14
+
15
+ ### Subflows specification
16
+ subflows_config:
17
+ Controller:
18
+ _target_: Tachi67.PlanWriterFlowModule.PlanWriterCtrlFlow.instantiate_from_default_config
19
+ backend:
20
+ api_infos: ???
21
+ model_name:
22
+ openai: gpt-4
23
+ azure: azure/gpt-4
24
+
25
+ Executor:
26
+ _target_: flows.base_flows.BranchingFlow.instantiate_from_default_config
27
+ subflows_config:
28
+ write_plan:
29
+ _target_: Tachi67.InteractivePlanGenFlowModule.InteractivePlanGenFlow.instantiate_from_default_config
30
+ subflows_config:
31
+ MemoryReading:
32
+ _target_: Tachi67.MemoryReadingFlowModule.MemoryReadingAtomicFlow.instantiate_from_default_config
33
+
34
+ PlanGenerator:
35
+ _target_: Tachi67.PlanGeneratorFlowModule.PlanGeneratorAtomicFlow.instantiate_from_default_config
36
+ backend:
37
+ api_infos: ???
38
+ model_name:
39
+ openai: gpt-4
40
+ azure: azure/gpt-4
41
+
42
+ PlanFileEditor:
43
+ _target_: Tachi67.PlanFileEditFlowModule.PlanFileEditAtomicFlow.instantiate_from_default_config
44
+
45
+ ParseFeedback:
46
+ _target_: Tachi67.ParseFeedbackFlowModule.ParseFeedbackAtomicFlow.instantiate_from_default_config
47
+ input_interface:
48
+ - "temp_plan_file_location"
49
+ - "temp_plan_file_written_timestamp"
50
+
51
+ ask_user:
52
+ _target_: Tachi67.PlanWriterFlowModule.PlanWriterAskUserFlow.instantiate_from_default_config
53
+
54
+
55
+ early_exit_key: "EARLY_EXIT"
56
+
57
+ topology:
58
+ - goal: "Select the next action and prepare the input for the executor."
59
+ input_interface:
60
+ _target_: flows.interfaces.KeyInterface
61
+ additional_transformations:
62
+ - _target_: flows.data_transformations.KeyMatchInput
63
+ flow: Controller
64
+ output_interface:
65
+ _target_: PlanWriterFlow.detect_finish_or_continue
66
+ reset: false
67
+
68
+ - goal: "Execute the action specified by the Controller."
69
+ input_interface:
70
+ _target_: flows.interfaces.KeyInterface
71
+ keys_to_rename:
72
+ command: branch
73
+ command_args: branch_input_data
74
+ keys_to_select: ["branch", "branch_input_data"]
75
+ flow: Executor
76
+ output_interface:
77
+ _target_: flows.interfaces.KeyInterface
78
+ keys_to_rename:
79
+ branch_output_data: observation
80
+ keys_to_unpack: ["observation"]
81
+ reset: false
__init__.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ dependencies = [
2
+ {"url": "Tachi67/ContentWriterFlowModule", "revision": "main"},
3
+ {"url": "Tachi67/InteractivePlanGenFlowModule", "revision": "main"},
4
+ {"url": "aiflows/ChatFlowModule", "revision": "a749ad10ed39776ba6721c37d0dc22af49ca0f17"},
5
+ {"url": "aiflows/HumanStandardInputFlowModule", "revision": "5683a922372c5fa90be9f6447d6662d8d80341fc"}
6
+ ]
7
+ from flows import flow_verse
8
+
9
+ flow_verse.sync_dependencies(dependencies)
10
+
11
+ from .PlanWriterFlow import PlanWriterFlow
12
+ from .PlanWriterCtrlFlow import PlanWriterCtrlFlow
13
+ from .PlanWriterAskUserFlow import PlanWriterAskUserFlow
run_planwriter.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ import hydra
4
+
5
+ from flows.backends.api_info import ApiInfo
6
+ from flows.messages import InputMessage
7
+ from flows.utils.general_helpers import read_yaml_file
8
+
9
+ from flows import logging
10
+ from flows.flow_cache import CACHING_PARAMETERS, clear_cache
11
+
12
+ CACHING_PARAMETERS.do_caching = False # Set to True in order to disable caching
13
+ # clear_cache() # Uncomment this line to clear the cache
14
+
15
+ logging.set_verbosity_debug()
16
+ logging.auto_set_dir()
17
+
18
+ dependencies = [
19
+ {"url": "Tachi67/ContentWriterFlowModule", "revision": "main"},
20
+ {"url": "Tachi67/InteractivePlanGenFlowModule", "revision": "main"},
21
+ {"url": "Tachi67/PlanWriterFlowModule", "revision": "main"},
22
+ {"url": "aiflows/ChatFlowModule", "revision": "a749ad10ed39776ba6721c37d0dc22af49ca0f17"},
23
+ ]
24
+
25
+ from flows import flow_verse
26
+
27
+ flow_verse.sync_dependencies(dependencies)
28
+
29
+ if __name__ == "__main__":
30
+ # ~~~ make sure to set the openai api key in the envs ~~~
31
+ key = os.getenv("OPENAI_API_KEY")
32
+ api_information = [ApiInfo(backend_used="openai", api_key=os.getenv("OPENAI_API_KEY"))]
33
+ path_to_output_file = None
34
+
35
+ # ~~~ setting api information into config ~~~
36
+ current_dir = os.getcwd()
37
+ cfg_path = os.path.join(current_dir, "PlanWriterFlow.yaml")
38
+ cfg = read_yaml_file(cfg_path)
39
+ cfg["subflows_config"]["Controller"]["backend"]["api_infos"] = api_information
40
+ cfg["subflows_config"]["Executor"]["subflows_config"]["write_plan"]["subflows_config"]["PlanGenerator"]["backend"]["api_infos"] = api_information
41
+
42
+ # ~~~ setting memory file of planner (empty dict)~~~
43
+ cfg["subflows_config"]["Executor"]["subflows_config"]["write_code"]["memory_files"] = {}
44
+
45
+ # ~~~ instantiating the flow and input data ~~~
46
+ PlanWriterFlow = hydra.utils.instantiate(cfg, _recursive_=False, _convert_="partial")
47
+
48
+ # ~~~ creating the plan file location (of the upper level flow e.g. ExtLib)
49
+ plan_file_location = os.path.join(current_dir, "ExtLib_plan.txt")
50
+
51
+ input_data = {
52
+ "goal": "create a function that adds two numbers and returns the result",
53
+ "plan_file_location": plan_file_location
54
+ }
55
+ input_message = InputMessage.build(
56
+ data_dict=input_data,
57
+ src_flow="Launcher",
58
+ dst_flow=PlanWriterFlow.name
59
+ )
60
+
61
+ # ~~~ calling the flow ~~~
62
+ output_message = PlanWriterFlow(input_message)