mirror of
https://github.com/OMGeeky/gpt-pilot.git
synced 2025-12-26 16:57:23 +01:00
#131 if process_name is specified to execute_command() keep the process alive
This commit is contained in:
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -45,4 +45,4 @@ jobs:
|
||||
run: |
|
||||
pip install pytest
|
||||
cd pilot
|
||||
PYTHONPATH=. pytest -m "not slow and not uses_tokens"
|
||||
PYTHONPATH=. pytest -m "not slow and not uses_tokens and not ux_test"
|
||||
|
||||
@@ -54,7 +54,6 @@ TODO:
|
||||
- Tasks provided as "programmatic goals" **(TODO: consider BDD)**
|
||||
|
||||
|
||||
|
||||
## Code Monkey
|
||||
**TODO: not listed in `ROLES`**
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ class Developer(Agent):
|
||||
data = step['command']
|
||||
# TODO END
|
||||
additional_message = 'Let\'s start with the step #0:\n\n' if i == 0 else f'So far, steps { ", ".join(f"#{j}" for j in range(i)) } are finished so let\'s do step #{i + 1} now.\n\n'
|
||||
return run_command_until_success(data['command'], data['timeout'], convo, additional_message=additional_message)
|
||||
return run_command_until_success(convo, data['command'], timeout=data['timeout'], additional_message=additional_message)
|
||||
|
||||
def step_human_intervention(self, convo, step: dict):
|
||||
"""
|
||||
@@ -108,7 +108,14 @@ class Developer(Agent):
|
||||
if self.run_command is not None else step['human_intervention_description']
|
||||
response = self.project.ask_for_human_intervention('I need human intervention:',
|
||||
human_intervention_description,
|
||||
cbs={ 'r': lambda conv: run_command_until_success(self.run_command, None, conv, force=True, return_cli_response=True) },
|
||||
cbs={
|
||||
'r': lambda conv: run_command_until_success(conv,
|
||||
self.run_command,
|
||||
process_name='app',
|
||||
timeout=None,
|
||||
force=True,
|
||||
return_cli_response=True)
|
||||
},
|
||||
convo=convo)
|
||||
|
||||
if 'user_input' not in response:
|
||||
@@ -136,6 +143,9 @@ class Developer(Agent):
|
||||
return { "success": llm_response == 'DONE', "cli_response": cli_response, "llm_response": llm_response }
|
||||
|
||||
def task_postprocessing(self, convo, development_task, continue_development, task_result, last_branch_name):
|
||||
# TODO: why does `run_command` belong to the Developer class, rather than just being passed?
|
||||
# ...It's set by execute_task() -> task_postprocessing(), but that is called by various sources.
|
||||
# What is it at step_human_intervention()?
|
||||
self.run_command = convo.send_message('development/get_run_command.prompt', {})
|
||||
if self.run_command.startswith('`'):
|
||||
self.run_command = self.run_command[1:]
|
||||
@@ -273,9 +283,14 @@ class Developer(Agent):
|
||||
'\nIf you want to run the app, ' + \
|
||||
yellow_bold('just type "r" and press ENTER and that will run `' + self.run_command + '`')
|
||||
# continue_description = ''
|
||||
# TODO: Wait for a specific string in the output or timeout?
|
||||
response = self.project.ask_for_human_intervention(
|
||||
user_description,
|
||||
cbs={ 'r': lambda convo: run_command_until_success(self.run_command, None, convo, force=True, return_cli_response=True, is_root_task=True) },
|
||||
cbs={'r': lambda convo: run_command_until_success(convo, self.run_command,
|
||||
process_name='app',
|
||||
timeout=None,
|
||||
force=True,
|
||||
return_cli_response=True, is_root_task=True)},
|
||||
convo=iteration_convo,
|
||||
is_root_task=True)
|
||||
|
||||
@@ -349,7 +364,7 @@ class Developer(Agent):
|
||||
|
||||
if installation_commands is not None:
|
||||
for cmd in installation_commands:
|
||||
run_command_until_success(cmd['command'], cmd['timeout'], self.convo_os_specific_tech)
|
||||
run_command_until_success(self.convo_os_specific_tech, cmd['command'], timeout=cmd['timeout'])
|
||||
|
||||
logger.info('The entire tech stack is installed and ready to be used.')
|
||||
|
||||
@@ -398,7 +413,7 @@ class Developer(Agent):
|
||||
test_type, description = convo.send_message('development/task/step_check.prompt', {}, GET_TEST_TYPE)
|
||||
|
||||
if test_type == 'command_test':
|
||||
return run_command_until_success(description['command'], description['timeout'], convo)
|
||||
return run_command_until_success(convo, description['command'], timeout=description['timeout'])
|
||||
elif test_type == 'automated_test':
|
||||
# TODO get code monkey to implement the automated test
|
||||
pass
|
||||
@@ -429,7 +444,7 @@ class Developer(Agent):
|
||||
}, EXECUTE_COMMANDS)
|
||||
if type == 'COMMAND':
|
||||
for cmd in step_details:
|
||||
run_command_until_success(cmd['command'], cmd['timeout'], convo)
|
||||
run_command_until_success(convo, cmd['command'], timeout=cmd['timeout'])
|
||||
# elif type == 'CODE_CHANGE':
|
||||
# code_changes_details = get_step_code_changes()
|
||||
# # TODO: give to code monkey for implementation
|
||||
|
||||
@@ -42,7 +42,7 @@ class TestDeveloper:
|
||||
@patch('helpers.AgentConvo.save_development_step')
|
||||
@patch('helpers.AgentConvo.create_gpt_chat_completion',
|
||||
return_value={'text': '{"command": "python --version", "timeout": 10}'})
|
||||
@patch('helpers.cli.execute_command', return_value=('', 'DONE'))
|
||||
@patch('helpers.cli.execute_command', return_value=('', 'DONE', None))
|
||||
def test_install_technology(self, mock_execute_command,
|
||||
mock_completion, mock_save, mock_get_saved_step):
|
||||
# Given
|
||||
@@ -61,7 +61,7 @@ class TestDeveloper:
|
||||
@patch('helpers.AgentConvo.create_gpt_chat_completion',
|
||||
return_value={'text': '{"type": "command_test", "command": {"command": "npm run test", "timeout": 3000}}'})
|
||||
# 2nd arg of return_value: `None` to debug, 'DONE' if successful
|
||||
@patch('helpers.cli.execute_command', return_value=('stdout:\n```\n\n```', 'DONE'))
|
||||
@patch('helpers.cli.execute_command', return_value=('stdout:\n```\n\n```', 'DONE', None))
|
||||
# @patch('helpers.cli.ask_user', return_value='yes')
|
||||
# @patch('helpers.cli.get_saved_command_run')
|
||||
def test_code_changes_command_test(self, mock_get_saved_step, mock_save, mock_chat_completion,
|
||||
@@ -126,7 +126,7 @@ class TestDeveloper:
|
||||
# Then
|
||||
assert result == {'success': True, 'user_input': 'no'}
|
||||
|
||||
@patch('helpers.cli.execute_command', return_value=('stdout:\n```\n\n```', 'DONE'))
|
||||
@patch('helpers.cli.execute_command', return_value=('stdout:\n```\n\n```', 'DONE', None))
|
||||
@patch('helpers.AgentConvo.get_saved_development_step')
|
||||
@patch('helpers.AgentConvo.save_development_step')
|
||||
@patch('utils.llm_connection.requests.post')
|
||||
|
||||
@@ -5,6 +5,7 @@ import threading
|
||||
import queue
|
||||
import time
|
||||
import platform
|
||||
from typing import Dict
|
||||
|
||||
from logger.logger import logger
|
||||
from utils.style import yellow, green, red, yellow_bold, white_bold
|
||||
@@ -16,6 +17,9 @@ from const.code_execution import MIN_COMMAND_RUN_TIME, MAX_COMMAND_RUN_TIME, MAX
|
||||
|
||||
interrupted = False
|
||||
|
||||
running_processes: Dict[str, int] = {}
|
||||
"""Holds a list of process IDs, mapped to the `process_name` provided in the call to `execute_command()`."""
|
||||
|
||||
|
||||
def enqueue_output(out, q):
|
||||
for line in iter(out.readline, ''):
|
||||
@@ -25,7 +29,7 @@ def enqueue_output(out, q):
|
||||
out.close()
|
||||
|
||||
|
||||
def run_command(command, root_path, q_stdout, q_stderr, pid_container):
|
||||
def run_command(command, root_path, q_stdout, q_stderr) -> subprocess.Popen:
|
||||
"""
|
||||
Execute a command in a subprocess.
|
||||
|
||||
@@ -34,12 +38,11 @@ def run_command(command, root_path, q_stdout, q_stderr, pid_container):
|
||||
root_path (str): The directory in which to run the command.
|
||||
q_stdout (Queue): A queue to capture stdout.
|
||||
q_stderr (Queue): A queue to capture stderr.
|
||||
pid_container (list): A list to store the process ID.
|
||||
|
||||
Returns:
|
||||
subprocess.Popen: The subprocess object.
|
||||
"""
|
||||
logger.info(f'Running `{command}`')
|
||||
logger.info(f'Running `{command}` on {platform.system()}')
|
||||
if platform.system() == 'Windows': # Check the operating system
|
||||
process = subprocess.Popen(
|
||||
command,
|
||||
@@ -60,7 +63,6 @@ def run_command(command, root_path, q_stdout, q_stderr, pid_container):
|
||||
cwd=root_path
|
||||
)
|
||||
|
||||
pid_container[0] = process.pid
|
||||
t_stdout = threading.Thread(target=enqueue_output, args=(process.stdout, q_stdout))
|
||||
t_stderr = threading.Thread(target=enqueue_output, args=(process.stderr, q_stderr))
|
||||
t_stdout.daemon = True
|
||||
@@ -70,7 +72,22 @@ def run_command(command, root_path, q_stdout, q_stderr, pid_container):
|
||||
return process
|
||||
|
||||
|
||||
def terminate_process(pid):
|
||||
def terminate_named_process(process_name: str) -> None:
|
||||
if process_name in running_processes:
|
||||
terminate_process(running_processes[process_name], process_name)
|
||||
|
||||
|
||||
def terminate_running_processes():
|
||||
for process_name in list(running_processes.keys()):
|
||||
terminate_process(running_processes[process_name], process_name)
|
||||
|
||||
|
||||
def terminate_process(pid: int, name=None) -> None:
|
||||
if name is None:
|
||||
logger.info('Terminating process %s', pid)
|
||||
else:
|
||||
logger.info('Terminating process "%s" (pid: %s)', name, pid)
|
||||
|
||||
if platform.system() == "Windows":
|
||||
try:
|
||||
subprocess.run(["taskkill", "/F", "/T", "/PID", str(pid)])
|
||||
@@ -82,8 +99,12 @@ def terminate_process(pid):
|
||||
except OSError as e:
|
||||
logger.error(f'Error while terminating process: {e}')
|
||||
|
||||
for process_name in list(running_processes.keys()):
|
||||
if running_processes[process_name] == pid:
|
||||
del running_processes[process_name]
|
||||
|
||||
def execute_command(project, command, timeout=None, force=False):
|
||||
|
||||
def execute_command(project, command, timeout=None, process_name: str = None, force=False):
|
||||
"""
|
||||
Execute a command and capture its output.
|
||||
|
||||
@@ -91,6 +112,8 @@ def execute_command(project, command, timeout=None, force=False):
|
||||
project: The project associated with the command.
|
||||
command (str): The command to run.
|
||||
timeout (int, optional): The maximum execution time in milliseconds. Default is None.
|
||||
process_name (str, optional): A name for the process.
|
||||
If `timeout` is not provided, can be used to terminate the process.
|
||||
force (bool, optional): Whether to execute the command without confirmation. Default is False.
|
||||
|
||||
Returns:
|
||||
@@ -98,6 +121,7 @@ def execute_command(project, command, timeout=None, force=False):
|
||||
or: '', 'DONE' if user answered 'no' or 'skip'
|
||||
llm_response (str): The response from the agent.
|
||||
TODO: this seems to be 'DONE' (no or skip) or None
|
||||
exit_code (int): The exit code of the process.
|
||||
"""
|
||||
if timeout is not None:
|
||||
if timeout < 1000:
|
||||
@@ -106,50 +130,60 @@ def execute_command(project, command, timeout=None, force=False):
|
||||
|
||||
if not force:
|
||||
print(yellow_bold(f'\n--------- EXECUTE COMMAND ----------'))
|
||||
answer = ask_user(
|
||||
project,
|
||||
f'Can I execute the command: `' + yellow_bold(command) + f'` with {timeout}ms timeout?',
|
||||
False,
|
||||
hint='If yes, just press ENTER'
|
||||
)
|
||||
question = f'Can I execute the command: `{yellow_bold(command)}`'
|
||||
if timeout is not None:
|
||||
question += f' with {timeout}ms timeout?'
|
||||
else:
|
||||
question += '?'
|
||||
|
||||
answer = ask_user(project, question, False, hint='If yes, just press ENTER')
|
||||
|
||||
# TODO: I think AutoGPT allows other feedback here, like:
|
||||
# "That's not going to work, let's do X instead"
|
||||
# We don't explicitly make "no" or "skip" options to the user
|
||||
# see https://github.com/Pythagora-io/gpt-pilot/issues/122
|
||||
print('answer: ' + answer)
|
||||
if answer == 'no':
|
||||
return '', 'DONE'
|
||||
return '', 'DONE', None
|
||||
elif answer == 'skip':
|
||||
return '', 'DONE'
|
||||
|
||||
return '', 'DONE', None
|
||||
|
||||
# TODO when a shell built-in commands (like cd or source) is executed, the output is not captured properly - this will need to be changed at some point
|
||||
# TODO: Windows support
|
||||
if "cd " in command or "source " in command:
|
||||
command = "bash -c '" + command + "'"
|
||||
|
||||
|
||||
project.command_runs_count += 1
|
||||
command_run = get_saved_command_run(project, command)
|
||||
if command_run is not None and project.skip_steps:
|
||||
# if we do, use it
|
||||
project.checkpoints['last_command_run'] = command_run
|
||||
print(yellow(f'Restoring command run response id {command_run.id}:\n```\n{command_run.cli_response}```'))
|
||||
return command_run.cli_response, None
|
||||
return command_run.cli_response, None, None
|
||||
|
||||
return_value = None
|
||||
|
||||
q_stderr = queue.Queue()
|
||||
q = queue.Queue()
|
||||
pid_container = [None]
|
||||
process = run_command(command, project.root_path, q, q_stderr, pid_container)
|
||||
process = run_command(command, project.root_path, q, q_stderr)
|
||||
|
||||
if process_name is not None:
|
||||
terminate_named_process(process_name)
|
||||
running_processes[process_name] = process.pid
|
||||
|
||||
output = ''
|
||||
stderr_output = ''
|
||||
start_time = time.time()
|
||||
interrupted = False
|
||||
|
||||
# Note: If we don't need to log the output in real-time, we can remove q, q_stderr, the threads and this while loop.
|
||||
# if timeout is not None:
|
||||
# timeout /= 1000
|
||||
# output, stderr_output = process.communicate(timeout=timeout)
|
||||
|
||||
try:
|
||||
while True and return_value is None:
|
||||
while True:
|
||||
elapsed_time = time.time() - start_time
|
||||
if timeout is not None:
|
||||
# TODO: print to IPC using a different message type so VS Code can ignore it or update the previous value
|
||||
@@ -158,7 +192,7 @@ def execute_command(project, command, timeout=None, force=False):
|
||||
# Check if process has finished
|
||||
if process.poll() is not None:
|
||||
# Get remaining lines from the queue
|
||||
time.sleep(0.1) # TODO this shouldn't be used
|
||||
time.sleep(0.1) # TODO this shouldn't be used
|
||||
while not q.empty():
|
||||
output_line = q.get_nowait()
|
||||
if output_line not in output:
|
||||
@@ -170,7 +204,7 @@ def execute_command(project, command, timeout=None, force=False):
|
||||
# If timeout is reached, kill the process
|
||||
if timeout is not None and elapsed_time * 1000 > timeout:
|
||||
raise TimeoutError("Command exceeded the specified timeout.")
|
||||
# os.killpg(pid_container[0], signal.SIGKILL)
|
||||
# os.killpg(process.pid, signal.SIGKILL)
|
||||
# break
|
||||
|
||||
try:
|
||||
@@ -193,6 +227,9 @@ def execute_command(project, command, timeout=None, force=False):
|
||||
stderr_output += stderr_line
|
||||
print(red('CLI ERROR:') + stderr_line, end='') # Print with different color for distinction
|
||||
logger.error('CLI ERROR: ' + stderr_line)
|
||||
|
||||
if process_name is not None:
|
||||
break
|
||||
|
||||
except (KeyboardInterrupt, TimeoutError) as e:
|
||||
interrupted = True
|
||||
@@ -203,7 +240,11 @@ def execute_command(project, command, timeout=None, force=False):
|
||||
print('\nTimeout detected. Stopping command execution...')
|
||||
logger.warn('Timeout detected. Stopping command execution...')
|
||||
|
||||
terminate_process(pid_container[0])
|
||||
terminate_process(process.pid)
|
||||
|
||||
elapsed_time = time.time() - start_time
|
||||
print(f'{command} took {round(elapsed_time * 1000)}ms to execute.')
|
||||
logger.info(f'{command} took {round(elapsed_time * 1000)}ms to execute.')
|
||||
|
||||
# stderr_output = ''
|
||||
# while not q_stderr.empty():
|
||||
@@ -215,9 +256,10 @@ def execute_command(project, command, timeout=None, force=False):
|
||||
return_value = 'stderr:\n```\n' + stderr_output[0:MAX_COMMAND_OUTPUT_LENGTH] + '\n```\n'
|
||||
return_value += 'stdout:\n```\n' + output[-MAX_COMMAND_OUTPUT_LENGTH:] + '\n```'
|
||||
|
||||
command_run = save_command_run(project, command, return_value)
|
||||
save_command_run(project, command, return_value)
|
||||
|
||||
return return_value, None, process.returncode
|
||||
|
||||
return return_value, None
|
||||
|
||||
def build_directory_tree(path, prefix="", ignore=None, is_last=False, files=None, add_descriptions=False):
|
||||
"""Build the directory tree structure in tree-like format.
|
||||
@@ -272,31 +314,57 @@ def execute_command_and_check_cli_response(command, timeout, convo):
|
||||
- llm_response (str): 'DONE' or 'NEEDS_DEBUGGING'
|
||||
"""
|
||||
# TODO: Prompt mentions `command` could be `INSTALLED` or `NOT_INSTALLED`, where is this handled?
|
||||
cli_response, llm_response = execute_command(convo.agent.project, command, timeout)
|
||||
cli_response, llm_response, exit_code = execute_command(convo.agent.project, command, timeout=timeout)
|
||||
if llm_response is None:
|
||||
llm_response = convo.send_message('dev_ops/ran_command.prompt',
|
||||
{ 'cli_response': cli_response, 'command': command })
|
||||
return cli_response, llm_response
|
||||
|
||||
|
||||
def run_command_until_success(command, timeout, convo, additional_message=None, force=False,
|
||||
return_cli_response=False, is_root_task=False):
|
||||
def run_command_until_success(convo, command,
|
||||
timeout: int | None,
|
||||
process_name: str | None = None,
|
||||
additional_message=None,
|
||||
force=False,
|
||||
return_cli_response=False,
|
||||
is_root_task=False):
|
||||
"""
|
||||
Run a command until it succeeds or reaches a timeout.
|
||||
|
||||
Args:
|
||||
convo (AgentConvo): The conversation object.
|
||||
command (str): The command to run.
|
||||
timeout (int): The maximum execution time in milliseconds.
|
||||
convo (AgentConvo): The conversation object.
|
||||
process_name: A name for the process.
|
||||
If `timeout` is not provided, can be used to terminate the process.
|
||||
additional_message (str, optional): Additional message to include in the response.
|
||||
force (bool, optional): Whether to execute the command without confirmation. Default is False.
|
||||
return_cli_response (bool, optional): If True, may raise TooDeepRecursionError(cli_response)
|
||||
is_root_task (bool, optional): If True and TokenLimitError is raised, will call `convo.load_branch(reset_branch_id)`
|
||||
"""
|
||||
cli_response, response = execute_command(convo.agent.project, command, timeout, force)
|
||||
cli_response, response, exit_code = execute_command(convo.agent.project,
|
||||
command,
|
||||
timeout=timeout,
|
||||
process_name=process_name,
|
||||
force=force)
|
||||
|
||||
if response is None:
|
||||
response = convo.send_message('dev_ops/ran_command.prompt',
|
||||
{'cli_response': cli_response, 'command': command, 'additional_message': additional_message})
|
||||
logger.info(f'{command} exit code: {exit_code}')
|
||||
if exit_code is None:
|
||||
response = 'DONE'
|
||||
else:
|
||||
# "I ran the command and the output was... respond with 'DONE' or 'NEEDS_DEBUGGING'"
|
||||
response = convo.send_message('dev_ops/ran_command.prompt',
|
||||
{
|
||||
'cli_response': cli_response,
|
||||
'command': command,
|
||||
'additional_message': additional_message,
|
||||
'exit_code': exit_code
|
||||
})
|
||||
logger.debug(f'LLM response: {response}')
|
||||
|
||||
if response != 'DONE':
|
||||
# 'NEEDS_DEBUGGING'
|
||||
print(red(f'Got incorrect CLI response:'))
|
||||
print(cli_response)
|
||||
print(red('-------------------'))
|
||||
|
||||
@@ -7,10 +7,9 @@ import sys
|
||||
import traceback
|
||||
from dotenv import load_dotenv
|
||||
load_dotenv()
|
||||
from helpers.ipc import IPCClient
|
||||
from const.ipc import MESSAGE_TYPE
|
||||
from utils.style import red
|
||||
|
||||
from utils.style import red
|
||||
from utils.custom_print import get_custom_print
|
||||
from helpers.Project import Project
|
||||
from utils.arguments import get_arguments
|
||||
from utils.exit import exit_gpt_pilot
|
||||
@@ -34,46 +33,9 @@ def init():
|
||||
return arguments
|
||||
|
||||
|
||||
def get_custom_print(args):
|
||||
built_in_print = builtins.print
|
||||
|
||||
def print_to_external_process(*args, **kwargs):
|
||||
# message = " ".join(map(str, args))
|
||||
message = args[0]
|
||||
|
||||
if 'type' not in kwargs:
|
||||
kwargs['type'] = 'verbose'
|
||||
elif kwargs['type'] == MESSAGE_TYPE['local']:
|
||||
local_print(*args, **kwargs)
|
||||
return
|
||||
|
||||
ipc_client_instance.send({
|
||||
'type': MESSAGE_TYPE[kwargs['type']],
|
||||
'content': message,
|
||||
})
|
||||
if kwargs['type'] == MESSAGE_TYPE['user_input_request']:
|
||||
return ipc_client_instance.listen()
|
||||
|
||||
def local_print(*args, **kwargs):
|
||||
message = " ".join(map(str, args))
|
||||
if 'type' in kwargs:
|
||||
if kwargs['type'] == MESSAGE_TYPE['info']:
|
||||
return
|
||||
del kwargs['type']
|
||||
|
||||
built_in_print(message, **kwargs)
|
||||
|
||||
ipc_client_instance = None
|
||||
if '--external-log-process-port' in args:
|
||||
ipc_client_instance = IPCClient(args['--external-log-process-port'])
|
||||
return print_to_external_process, ipc_client_instance
|
||||
else:
|
||||
return local_print, ipc_client_instance
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
# sys.argv.append('--ux-test=' + 'run_command_until_success')
|
||||
# sys.argv.append('--ux-test=' + 'continue_development')
|
||||
args = init()
|
||||
|
||||
builtins.print, ipc_client_instance = get_custom_print(args)
|
||||
@@ -83,7 +45,7 @@ if __name__ == "__main__":
|
||||
print({ 'db_data': get_created_apps_with_steps() }, type='info')
|
||||
elif '--ux-test' in args:
|
||||
from test.ux_tests import run_test
|
||||
run_test(args['--ux-test'])
|
||||
run_test(args['--ux-test'], args)
|
||||
else:
|
||||
# TODO get checkpoint from database and fill the project with it
|
||||
project = Project(args, ipc_client_instance=ipc_client_instance)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
{{ additional_info }}I ran the command `{{ command }}` and for this response from CLI:
|
||||
{{ additional_info }}I ran the command `{{ command }}`
|
||||
{%- if exit_code %}, the exit code was {{ exit_code }} {% endif -%}
|
||||
and the output was:
|
||||
```
|
||||
{{ cli_response }}
|
||||
```
|
||||
|
||||
If the command was successfully executed, respond with `DONE` and if it wasn't, respond with `NEEDS_DEBUGGING`.
|
||||
If the command was successfully executed, respond with `DONE` and if it wasn't, respond with `NEEDS_DEBUGGING`.
|
||||
|
||||
36
pilot/test/ux_tests/Dev_continue_development.py
Normal file
36
pilot/test/ux_tests/Dev_continue_development.py
Normal file
@@ -0,0 +1,36 @@
|
||||
import pytest
|
||||
from unittest.mock import patch
|
||||
|
||||
from helpers.AgentConvo import AgentConvo
|
||||
from helpers.agents import Developer
|
||||
from .utils import create_project
|
||||
from helpers.cli import terminate_running_processes
|
||||
from test.mock_questionary import MockQuestionary
|
||||
|
||||
|
||||
@pytest.mark.ux_test
|
||||
@patch('utils.questionary.get_saved_user_input')
|
||||
@patch('helpers.cli.get_saved_command_run')
|
||||
@patch('helpers.AgentConvo.get_saved_development_step')
|
||||
@patch('helpers.AgentConvo.save_development_step')
|
||||
def test_continue_development(mock_4, mock_3, mock_2, mock_1):
|
||||
# Given
|
||||
project = create_project('continue_development', 'hello_world_server')
|
||||
# execute_command(project, 'npm install', 13000)
|
||||
|
||||
developer = Developer(project)
|
||||
project.developer = developer
|
||||
convo = AgentConvo(developer)
|
||||
convo.load_branch = lambda last_branch_name: None
|
||||
developer.run_command = 'node server.js'
|
||||
|
||||
# Note: uncomment the following 2 lines and indent the remaining lines when debugging without console input
|
||||
mock_questionary = MockQuestionary(['r', 'continue'])
|
||||
with patch('utils.questionary.questionary', mock_questionary):
|
||||
|
||||
# When
|
||||
# `continue_development` calls `run_command_until_success()` if the user types "r"
|
||||
developer.continue_development(convo, 'branch_name', 'The web page should say "Hello, World!"')
|
||||
print('end of "continue_development" scenario')
|
||||
|
||||
terminate_running_processes()
|
||||
@@ -1,10 +1,20 @@
|
||||
from .run_command_until_success import run_command_until_success
|
||||
# from .run_command_until_success import run_command_until_success
|
||||
from .cli_execute_command import cli_execute_command
|
||||
from .Dev_continue_development import test_continue_development
|
||||
from .utils import use_args
|
||||
|
||||
|
||||
def run_test(test_name: str):
|
||||
def run_test(test_name: str, args):
|
||||
print(f'Running UX test "{test_name}"...')
|
||||
|
||||
if test_name == 'run_command_until_success':
|
||||
return run_command_until_success()
|
||||
tests = {
|
||||
# 'run_command_until_success': run_command_until_success,
|
||||
'cli_execute_command': cli_execute_command,
|
||||
'continue_development': test_continue_development,
|
||||
}
|
||||
|
||||
if test_name in tests:
|
||||
use_args(args)
|
||||
return tests[test_name]()
|
||||
|
||||
print(f'UX test "{test_name}" not found')
|
||||
|
||||
40
pilot/utils/custom_print.py
Normal file
40
pilot/utils/custom_print.py
Normal file
@@ -0,0 +1,40 @@
|
||||
import builtins
|
||||
from helpers.ipc import IPCClient
|
||||
from const.ipc import MESSAGE_TYPE
|
||||
|
||||
|
||||
def get_custom_print(args):
|
||||
built_in_print = builtins.print
|
||||
|
||||
def print_to_external_process(*args, **kwargs):
|
||||
# message = " ".join(map(str, args))
|
||||
message = args[0]
|
||||
|
||||
if 'type' not in kwargs:
|
||||
kwargs['type'] = 'verbose'
|
||||
elif kwargs['type'] == MESSAGE_TYPE['local']:
|
||||
local_print(*args, **kwargs)
|
||||
return
|
||||
|
||||
ipc_client_instance.send({
|
||||
'type': MESSAGE_TYPE[kwargs['type']],
|
||||
'content': message,
|
||||
})
|
||||
if kwargs['type'] == MESSAGE_TYPE['user_input_request']:
|
||||
return ipc_client_instance.listen()
|
||||
|
||||
def local_print(*args, **kwargs):
|
||||
message = " ".join(map(str, args))
|
||||
if 'type' in kwargs:
|
||||
if kwargs['type'] == MESSAGE_TYPE['info']:
|
||||
return
|
||||
del kwargs['type']
|
||||
|
||||
built_in_print(message, **kwargs)
|
||||
|
||||
ipc_client_instance = None
|
||||
if '--external-log-process-port' in args:
|
||||
ipc_client_instance = IPCClient(args['--external-log-process-port'])
|
||||
return print_to_external_process, ipc_client_instance
|
||||
else:
|
||||
return local_print, ipc_client_instance
|
||||
@@ -3,6 +3,7 @@ import os
|
||||
import hashlib
|
||||
import requests
|
||||
|
||||
from helpers.cli import terminate_running_processes
|
||||
from utils.questionary import get_user_feedback
|
||||
|
||||
|
||||
@@ -43,6 +44,7 @@ def get_path_id():
|
||||
|
||||
|
||||
def exit_gpt_pilot(ask_feedback=True):
|
||||
terminate_running_processes()
|
||||
path_id = get_path_id()
|
||||
send_telemetry(path_id)
|
||||
|
||||
|
||||
@@ -262,7 +262,8 @@ def stream_gpt_completion(data, req_type, project):
|
||||
model = os.getenv('MODEL_NAME', 'gpt-4')
|
||||
endpoint = os.getenv('ENDPOINT')
|
||||
|
||||
logger.info(f'> Request model: {model} ({data["model"]}) messages: {data["messages"]}')
|
||||
logger.info(f'> Request model: {model} ({data["model"]})\n'
|
||||
+ '\n'.join([f"{message['role']}: {message['content']}" for message in data['messages']]))
|
||||
|
||||
if endpoint == 'AZURE':
|
||||
# If yes, get the AZURE_ENDPOINT from .ENV file
|
||||
|
||||
@@ -5,4 +5,5 @@ python_files = test_*.py
|
||||
markers =
|
||||
slow: marks tests as slow (deselect with '-m "not slow"')
|
||||
uses_tokens: Integration tests which use tokens
|
||||
ux_test: Tests which are used to test the UX
|
||||
daily: tests which should be run daily
|
||||
|
||||
@@ -12,6 +12,7 @@ prompt-toolkit==3.0.39
|
||||
psycopg2-binary==2.9.6
|
||||
python-dotenv==1.0.0
|
||||
python-editor==1.0.4
|
||||
pytest==7.4.2
|
||||
questionary==1.10.0
|
||||
readchar==4.0.5
|
||||
regex==2023.6.3
|
||||
|
||||
Reference in New Issue
Block a user