Upload 7 files
Browse files- CodeWriterAskUserFlow.py +31 -0
- CodeWriterAskUserFlow.yaml +8 -0
- CodeWriterCtrlFlow.py +84 -0
- CodeWriterCtrlFlow.yaml +111 -0
- CodeWriterFlow.py +43 -0
- CodeWriterFlow.yaml +76 -0
- __init__.py +14 -0
CodeWriterAskUserFlow.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 CodeWriterAskUserFlow(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["human_feedback"] = human_input
|
29 |
+
response["code"] = "no code was written"
|
30 |
+
|
31 |
+
return response
|
CodeWriterAskUserFlow.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"
|
CodeWriterCtrlFlow.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 CodeWriterCtrlFlow(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 code writer, the tester, or to finish",
|
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)
|
CodeWriterCtrlFlow.yaml
ADDED
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
name: "CodeWriterControllerFlow"
|
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 |
+
- "code"
|
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_code:
|
32 |
+
description: "Write code to finish the goal with user interaction"
|
33 |
+
input_args: ["goal"]
|
34 |
+
finish:
|
35 |
+
description: "Signal that the objective has been satisfied, and returns the answer to the user."
|
36 |
+
input_args: []
|
37 |
+
ask_user:
|
38 |
+
description: "Ask user a question for confirmation or assistance"
|
39 |
+
input_args: ["question"]
|
40 |
+
test:
|
41 |
+
description: "test the code generated from write_code"
|
42 |
+
input_args: []
|
43 |
+
|
44 |
+
system_message_prompt_template:
|
45 |
+
_target_: langchain.PromptTemplate
|
46 |
+
template: |2-
|
47 |
+
You are in charge of a department of writing code to solve a certain goal. You work with a coder, who does all the coding job; and a code tester, who does all the testing job.
|
48 |
+
|
49 |
+
Your **ONLY** task is to take the user's goal for you, to decide whether to call the coder to write or re-write the code, to call the tester to test the code, or to finish the current task.
|
50 |
+
|
51 |
+
When you need to call the code writer, call the `write_code` command with the goal specified.
|
52 |
+
When you need to call the code tester, call the `test` command to test the code written.
|
53 |
+
When the code is written and the user is satisfied, call the `finish` command to terminate the current process.
|
54 |
+
Whenever you are in doubt, or need to confirm something to the user, call `ask_user` with the question.
|
55 |
+
|
56 |
+
The coder will only write one function per goal, make sure you are not asking the coder to write more than one function.
|
57 |
+
|
58 |
+
You **must not** write code yourself. You only decide whether to call the coder with specified goals or to finish.
|
59 |
+
|
60 |
+
Your workflow:
|
61 |
+
1. Upon user request, call the `write_code` with the goal given.
|
62 |
+
2. The coder will write code, which is a function. The user will examine the code, and provide feedback.
|
63 |
+
3. Depending on the feedback of the user:
|
64 |
+
3.1. The user provides feedback on how to change the code, **call the coder with user's specific requirements again, to ask the coder to refine the code**. Go back to step 2.
|
65 |
+
3.2. The user does not provide details about refining the code, for example, just stating the fact that the user has updated the code, **this means the user is satisfied with the code written, call the `finish` command.**
|
66 |
+
4. If the user is satisfied with the code, call `test` to test the code
|
67 |
+
5. Depending on the result of the test:
|
68 |
+
5.1 Test passes, terminate the current process with the `finish command`
|
69 |
+
5.2 Test fails, **call the coder with details of the test results to instruct the coder to refine the code**, go back to step 2.
|
70 |
+
|
71 |
+
If you have completed all your tasks, make sure to use the "finish" command.
|
72 |
+
|
73 |
+
Constraints:
|
74 |
+
1. Exclusively use the commands listed in double quotes e.g. "command name"
|
75 |
+
|
76 |
+
Your response **MUST** be in the following format:
|
77 |
+
Response Format:
|
78 |
+
{
|
79 |
+
"command": "call code writer, the tester, or to finish",
|
80 |
+
"command_args": {
|
81 |
+
"arg name": "value"
|
82 |
+
}
|
83 |
+
}
|
84 |
+
Ensure your responses can be parsed by Python json.loads
|
85 |
+
|
86 |
+
|
87 |
+
Available Functions:
|
88 |
+
{{commands}}
|
89 |
+
input_variables: ["commands"]
|
90 |
+
template_format: jinja2
|
91 |
+
|
92 |
+
human_message_prompt_template:
|
93 |
+
_target_: flows.prompt_template.JinjaPrompt
|
94 |
+
template: |2-
|
95 |
+
Here is the code written by the coder, it might have been updated by the user, depending on the user's feedback:
|
96 |
+
{{code}}
|
97 |
+
Here is the feedback, depending on the last command you called, it either came from the user or the tester:
|
98 |
+
{{feedback}}
|
99 |
+
input_variables:
|
100 |
+
- "code"
|
101 |
+
- "feedback"
|
102 |
+
template_format: jinja2
|
103 |
+
|
104 |
+
init_human_message_prompt_template:
|
105 |
+
_target_: flows.prompt_template.JinjaPrompt
|
106 |
+
template: |2-
|
107 |
+
Here is the goal you need to achieve:
|
108 |
+
{{goal}}
|
109 |
+
input_variables:
|
110 |
+
- "goal"
|
111 |
+
template_format: jinja2
|
CodeWriterFlow.py
ADDED
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 CodeWriterFlow(ContentWriterFlow):
|
9 |
+
def _on_reach_max_round(self):
|
10 |
+
self._state_update_dict({
|
11 |
+
"code": "The maximum amount of rounds was reached before the model generated the code.",
|
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 |
+
# ~~~ delete the temp code file ~~~
|
20 |
+
keys_to_fetch_from_state = ["temp_code_file_location", "code"]
|
21 |
+
fetched_state = self._fetch_state_attributes_by_keys(keys=keys_to_fetch_from_state)
|
22 |
+
temp_code_file_location = fetched_state["temp_code_file_location"]
|
23 |
+
code_content = fetched_state["code"]
|
24 |
+
if os.path.exists(temp_code_file_location):
|
25 |
+
os.remove(temp_code_file_location)
|
26 |
+
# ~~~ return the code content ~~~
|
27 |
+
return {
|
28 |
+
"EARLY_EXIT": True,
|
29 |
+
"code": code_content,
|
30 |
+
"status": "finished"
|
31 |
+
}
|
32 |
+
elif command == "test":
|
33 |
+
# ~~~ fetch code string from flow state ~~~
|
34 |
+
keys_to_fetch_from_state = ["code"]
|
35 |
+
fetched_state = self._fetch_state_attributes_by_keys(keys=keys_to_fetch_from_state)
|
36 |
+
|
37 |
+
# ~~~ add code content to the command args (branch input data) ~~~
|
38 |
+
code_content = fetched_state["code"]
|
39 |
+
output_payload["command_args"]["code"] = code_content
|
40 |
+
|
41 |
+
return output_payload
|
42 |
+
else:
|
43 |
+
return output_payload
|
CodeWriterFlow.yaml
ADDED
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
name: "CodeWriter"
|
2 |
+
description: "Generates code with interactions with the user"
|
3 |
+
|
4 |
+
_target_: Tachi67.CodeWriterFlowModule.CodeWriterFlow.instantiate_from_default_config
|
5 |
+
|
6 |
+
output_interface:
|
7 |
+
- "code"
|
8 |
+
- "status"
|
9 |
+
|
10 |
+
### Subflows specification
|
11 |
+
subflows_config:
|
12 |
+
Controller:
|
13 |
+
_target_: Tachi67.CodeWriterFlowModule.CodeWriterCtrlFlow.instantiate_from_default_config
|
14 |
+
backend:
|
15 |
+
api_infos: ???
|
16 |
+
model_name:
|
17 |
+
openai: gpt-4
|
18 |
+
azure: azure/gpt-4
|
19 |
+
|
20 |
+
Executor:
|
21 |
+
_target_: flows.base_flows.BranchingFlow.instantiate_from_default_config
|
22 |
+
subflows_config:
|
23 |
+
write_code:
|
24 |
+
_target_: Tachi67.InteractiveCodeGenFlowModule.InteractiveCodeGenFlow.instantiate_from_default_config
|
25 |
+
subflows_config:
|
26 |
+
MemoryReading:
|
27 |
+
_target_: Tachi67.MemoryReadingFlowModule.MemoryReadingAtomicFlow.instantiate_from_default_config
|
28 |
+
|
29 |
+
CodeGenerator:
|
30 |
+
_target_: Tachi67.CodeGeneratorFlowModule.CodeGeneratorAtomicFlow.instantiate_from_default_config
|
31 |
+
backend:
|
32 |
+
api_infos: ???
|
33 |
+
model_name:
|
34 |
+
openai: gpt-4
|
35 |
+
azure: azure/gpt-4
|
36 |
+
|
37 |
+
CodeFileEditor:
|
38 |
+
_target_: Tachi67.CodeFileEditFlowModule.CodeFileEditAtomicFlow.instantiate_from_default_config
|
39 |
+
|
40 |
+
ParseFeedback:
|
41 |
+
_target_: Tachi67.ParseFeedbackFlowModule.ParseFeedbackAtomicFlow.instantiate_from_default_config
|
42 |
+
|
43 |
+
ask_user:
|
44 |
+
_target_: Tachi67.CodeWriterFlowModule.CodeWriterAskUserFlow.instantiate_from_default_config
|
45 |
+
|
46 |
+
test:
|
47 |
+
_target_: Tachi67.TestCodeFlowModule.TestCodeFlow.instantiate_from_default_config
|
48 |
+
|
49 |
+
|
50 |
+
early_exit_key: "EARLY_EXIT"
|
51 |
+
|
52 |
+
topology:
|
53 |
+
- goal: "Select the next action and prepare the input for the executor."
|
54 |
+
input_interface:
|
55 |
+
_target_: flows.interfaces.KeyInterface
|
56 |
+
additional_transformations:
|
57 |
+
- _target_: flows.data_transformations.KeyMatchInput
|
58 |
+
flow: Controller
|
59 |
+
output_interface:
|
60 |
+
_target_: CodeWriterFlow.detect_finish_or_continue
|
61 |
+
reset: false
|
62 |
+
|
63 |
+
- goal: "Execute the action specified by the Controller."
|
64 |
+
input_interface:
|
65 |
+
_target_: flows.interfaces.KeyInterface
|
66 |
+
keys_to_rename:
|
67 |
+
command: branch
|
68 |
+
command_args: branch_input_data
|
69 |
+
keys_to_select: ["branch", "branch_input_data"]
|
70 |
+
flow: Executor
|
71 |
+
output_interface:
|
72 |
+
_target_: flows.interfaces.KeyInterface
|
73 |
+
keys_to_rename:
|
74 |
+
branch_output_data: observation
|
75 |
+
keys_to_unpack: ["observation"]
|
76 |
+
reset: false
|
__init__.py
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
dependencies = [
|
2 |
+
{"url": "Tachi67/ContentWriterFlowModule", "revision": "main"},
|
3 |
+
{"url": "Tachi67/InteractiveCodeGenFlowModule", "revision": "main"},
|
4 |
+
{"url": "Tachi67/TestCodeFlowModule", "revision": "main"},
|
5 |
+
{"url": "aiflows/ChatFlowModule", "revision": "a749ad10ed39776ba6721c37d0dc22af49ca0f17"},
|
6 |
+
{"url": "aiflows/HumanStandardInputFlowModule", "revision": "5683a922372c5fa90be9f6447d6662d8d80341fc"}
|
7 |
+
]
|
8 |
+
from flows import flow_verse
|
9 |
+
|
10 |
+
flow_verse.sync_dependencies(dependencies)
|
11 |
+
|
12 |
+
from .CodeWriterFlow import CodeWriterFlow
|
13 |
+
from .CodeWriterCtrlFlow import CodeWriterCtrlFlow
|
14 |
+
from .CodeWriterAskUserFlow import CodeWriterAskUserFlow
|