From deaea68f4f6df57eda0a278b65907e66e47ef1db Mon Sep 17 00:00:00 2001 From: Joschka Braun Date: Mon, 17 Apr 2023 15:01:28 +0200 Subject: [PATCH 1/9] refactor: langchain --- requirements.txt | 3 +- src/apis/gpt.py | 124 +++++++++----------------- src/cli.py | 7 +- src/constants.py | 6 -- src/options/generate/generator.py | 44 ++++----- src/options/generate/prompt_system.py | 2 +- src/utils/io.py | 22 ----- 7 files changed, 76 insertions(+), 132 deletions(-) diff --git a/requirements.txt b/requirements.txt index bc0630d..b8c59f4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,5 @@ openai psutil jina jcloud -jina-hubble-sdk \ No newline at end of file +jina-hubble-sdk +langchain \ No newline at end of file diff --git a/src/apis/gpt.py b/src/apis/gpt.py index d27983f..eb0a582 100644 --- a/src/apis/gpt.py +++ b/src/apis/gpt.py @@ -1,15 +1,21 @@ import os from time import sleep -from typing import List, Tuple, Optional +from typing import List, Any import openai -from openai.error import RateLimitError, Timeout, APIConnectionError +from langchain.callbacks import CallbackManager +from langchain.chat_models import ChatOpenAI +from openai.error import RateLimitError +from langchain.schema import ( + AIMessage, + HumanMessage, + SystemMessage, + BaseMessage +) +from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler -from src.constants import PRICING_GPT4_PROMPT, PRICING_GPT4_GENERATION, PRICING_GPT3_5_TURBO_PROMPT, \ - PRICING_GPT3_5_TURBO_GENERATION, CHARS_PER_TOKEN -from src.options.generate.prompt_system import system_base_definition, executor_example, docarray_example, client_example -from src.utils.io import timeout_generator_wrapper, GenerationTimeoutError +from src.options.generate.prompt_system import system_message_base, executor_example, docarray_example, client_example from src.utils.string_tools import print_colored @@ -18,19 +24,14 @@ class GPTSession: self.configure_openai_api_key() if model == 'gpt-4' and self.is_gpt4_available(): self.supported_model = 'gpt-4' - self.pricing_prompt = PRICING_GPT4_PROMPT - self.pricing_generation = PRICING_GPT4_GENERATION else: if model == 'gpt-4': print_colored('GPT-4 is not available. Using GPT-3.5-turbo instead.', 'yellow') model = 'gpt-3.5-turbo' self.supported_model = model - self.pricing_prompt = PRICING_GPT3_5_TURBO_PROMPT - self.pricing_generation = PRICING_GPT3_5_TURBO_GENERATION - self.chars_prompt_so_far = 0 - self.chars_generation_so_far = 0 - def configure_openai_api_key(self): + @staticmethod + def configure_openai_api_key(): if 'OPENAI_API_KEY' not in os.environ: raise Exception(''' You need to set OPENAI_API_KEY in your environment. @@ -39,7 +40,8 @@ If you have updated it already, please restart your terminal. ) openai.api_key = os.environ['OPENAI_API_KEY'] - def is_gpt4_available(self): + @staticmethod + def is_gpt4_available(): try: for i in range(5): try: @@ -58,87 +60,47 @@ If you have updated it already, please restart your terminal. except openai.error.InvalidRequestError: return False - def cost_callback(self, chars_prompt, chars_generation): - self.chars_prompt_so_far += chars_prompt - self.chars_generation_so_far += chars_generation - print('\n') - money_prompt = self.calculate_money_spent(self.chars_prompt_so_far, self.pricing_prompt) - money_generation = self.calculate_money_spent(self.chars_generation_so_far, self.pricing_generation) - print('Total money spent so far on openai.com:', f'${money_prompt + money_generation}') - print('\n') - def get_conversation(self, system_definition_examples: List[str] = ['executor', 'docarray', 'client']): - return _GPTConversation(self.supported_model, self.cost_callback, system_definition_examples) + return _GPTConversation(self.supported_model, system_definition_examples) - def calculate_money_spent(self, num_chars, price): - return round(num_chars / CHARS_PER_TOKEN * price / 1000, 3) + +class AssistantStreamingStdOutCallbackHandler(StreamingStdOutCallbackHandler): + def on_llm_new_token(self, token: str, **kwargs: Any) -> None: + """Run on new LLM token. Only available when streaming is enabled.""" + if os.environ['VERBOSE'].lower() == 'true': + print_colored('', token, 'green', end='') class _GPTConversation: - def __init__(self, model: str, cost_callback, system_definition_examples: List[str] = ['executor', 'docarray', 'client']): - self.model = model - self.cost_callback = cost_callback - self.prompt_list: List[Optional[Tuple]] = [None] - self.set_system_definition(system_definition_examples) + def __init__(self, model: str, system_definition_examples: List[str] = ['executor', 'docarray', 'client']): + self.chat = ChatOpenAI( + model_name=model, + streaming=True, + callback_manager=CallbackManager([AssistantStreamingStdOutCallbackHandler()]), + temperature=0 + ) + self.messages: List[BaseMessage] = [] + self.system_message = self._create_system_message(system_definition_examples) if os.environ['VERBOSE'].lower() == 'true': - print_colored('system', self.prompt_list[0][1], 'magenta') + print_colored('system', self.system_message.content, 'magenta') - def query(self, prompt: str): + def chat(self, prompt: str): + chat_message = HumanMessage(content=prompt) + self.messages.append(chat_message) if os.environ['VERBOSE'].lower() == 'true': print_colored('user', prompt, 'blue') - self.prompt_list.append(('user', prompt)) - response = self.get_response(self.prompt_list) - self.prompt_list.append(('assistant', response)) + print_colored('assistant', '', 'green', end='') + response = self.chat([self.system_message] + self.messages) + self.messages.append(AIMessage(content=response)) return response - def set_system_definition(self, system_definition_examples: List[str] = []): - system_message = system_base_definition + @staticmethod + def _create_system_message(system_definition_examples: List[str] = []) -> SystemMessage: + system_message = system_message_base if 'executor' in system_definition_examples: system_message += f'\n{executor_example}' if 'docarray' in system_definition_examples: system_message += f'\n{docarray_example}' if 'client' in system_definition_examples: system_message += f'\n{client_example}' - self.prompt_list[0] = ('system', system_message) - - def get_response_from_stream(self, response_generator): - response_generator_with_timeout = timeout_generator_wrapper(response_generator, 10) - complete_string = '' - for chunk in response_generator_with_timeout: - delta = chunk['choices'][0]['delta'] - if 'content' in delta: - content = delta['content'] - print_colored('' if complete_string else 'assistant', content, 'green', end='') - complete_string += content - return complete_string - - def get_response(self, prompt_list: List[Tuple[str, str]]): - for i in range(10): - try: - response_generator = openai.ChatCompletion.create( - temperature=0, - max_tokens=None, - model=self.model, - stream=True, - messages=[ - { - "role": prompt[0], - "content": prompt[1] - } - for prompt in prompt_list - ] - ) - - complete_string = self.get_response_from_stream(response_generator) - - except (RateLimitError, Timeout, ConnectionError, APIConnectionError, GenerationTimeoutError) as e: - print('/n', e) - print('retrying...') - sleep(3) - continue - chars_prompt = sum(len(prompt[1]) for prompt in prompt_list) - chars_generation = len(complete_string) - self.cost_callback(chars_prompt, chars_generation) - return complete_string - raise Exception('Failed to get response') - + return SystemMessage(content=system_message) diff --git a/src/cli.py b/src/cli.py index db24889..11f9ac4 100644 --- a/src/cli.py +++ b/src/cli.py @@ -2,6 +2,7 @@ import functools import os import click +from langchain.callbacks import get_openai_callback from src.apis.jina_cloud import jina_auth_login from src.options.configure.key_handling import set_api_key @@ -64,7 +65,11 @@ def generate( from src.options.generate.generator import Generator generator = Generator(model=model) - generator.generate(description, test, path) + with get_openai_callback() as cb: + generator.generate(description, test, path) + print(f"Prompt/Completion/Total Tokens: {cb.prompt_tokens}/{cb.completion_tokens}/{cb.total_tokens}") + print(f"Total Cost on OpenAI (USD): ${cb.total_cost}") + @main.command() @path_param diff --git a/src/constants.py b/src/constants.py index 6eef6ae..1bd9746 100644 --- a/src/constants.py +++ b/src/constants.py @@ -23,12 +23,6 @@ FILE_AND_TAG_PAIRS = [ FLOW_URL_PLACEHOLDER = 'jcloud.jina.ai' -PRICING_GPT4_PROMPT = 0.03 -PRICING_GPT4_GENERATION = 0.06 -PRICING_GPT3_5_TURBO_PROMPT = 0.002 -PRICING_GPT3_5_TURBO_GENERATION = 0.002 - -CHARS_PER_TOKEN = 3.4 NUM_IMPLEMENTATION_STRATEGIES = 5 MAX_DEBUGGING_ITERATIONS = 10 diff --git a/src/options/generate/generator.py b/src/options/generate/generator.py index 875b417..21cb508 100644 --- a/src/options/generate/generator.py +++ b/src/options/generate/generator.py @@ -74,15 +74,15 @@ class Generator: + executor_file_task(microservice_name, description, test, package) ) conversation = self.gpt_session.get_conversation() - microservice_content_raw = conversation.query(user_query) + microservice_content_raw = conversation.chat(user_query) if is_chain_of_thought: - microservice_content_raw = conversation.query( + microservice_content_raw = conversation.chat( f"General rules: " + not_allowed_executor() + chain_of_thought_optimization('python', 'microservice.py')) microservice_content = self.extract_content_from_result(microservice_content_raw, 'microservice.py', match_single_block=True) if microservice_content == '': - microservice_content_raw = conversation.query('You must add the executor code.') + microservice_content_raw = conversation.chat('You must add the executor code.') microservice_content = self.extract_content_from_result( microservice_content_raw, 'microservice.py', match_single_block=True ) @@ -95,9 +95,9 @@ class Generator: + test_executor_file_task(microservice_name, test) ) conversation = self.gpt_session.get_conversation() - test_microservice_content_raw = conversation.query(user_query) + test_microservice_content_raw = conversation.chat(user_query) if is_chain_of_thought: - test_microservice_content_raw = conversation.query( + test_microservice_content_raw = conversation.chat( f"General rules: " + not_allowed_executor() + chain_of_thought_optimization('python', 'test_microservice.py') + "Don't add any additional tests. " @@ -116,9 +116,9 @@ class Generator: + requirements_file_task() ) conversation = self.gpt_session.get_conversation() - requirements_content_raw = conversation.query(user_query) + requirements_content_raw = conversation.chat(user_query) if is_chain_of_thought: - requirements_content_raw = conversation.query( + requirements_content_raw = conversation.chat( chain_of_thought_optimization('', requirements_path) + "Keep the same version of jina ") requirements_content = self.extract_content_from_result(requirements_content_raw, 'requirements.txt', @@ -134,9 +134,9 @@ class Generator: + docker_file_task() ) conversation = self.gpt_session.get_conversation() - dockerfile_content_raw = conversation.query(user_query) + dockerfile_content_raw = conversation.chat(user_query) if is_chain_of_thought: - dockerfile_content_raw = conversation.query( + dockerfile_content_raw = conversation.chat( f"General rules: " + not_allowed_executor() + chain_of_thought_optimization('dockerfile', 'Dockerfile')) dockerfile_content = self.extract_content_from_result(dockerfile_content_raw, 'Dockerfile', match_single_block=True) @@ -172,8 +172,8 @@ The playground (app.py) must not let the user configure the host on the ui. ''' ) conversation = self.gpt_session.get_conversation([]) - conversation.query(user_query) - playground_content_raw = conversation.query(chain_of_thought_optimization('python', 'app.py', 'the playground')) + conversation.chat(user_query) + playground_content_raw = conversation.chat(chain_of_thought_optimization('python', 'app.py', 'the playground')) playground_content = self.extract_content_from_result(playground_content_raw, 'app.py', match_single_block=True) persist_file(playground_content, os.path.join(microservice_path, 'app.py')) @@ -213,7 +213,7 @@ The playground (app.py) must not let the user configure the host on the ui. user_query = self.get_user_query_code_issue(description, error, file_name_to_content, test) conversation = self.gpt_session.get_conversation() - returned_files_raw = conversation.query(user_query) + returned_files_raw = conversation.chat(user_query) for file_name, tag in FILE_AND_TAG_PAIRS: updated_file = self.extract_content_from_result(returned_files_raw, file_name) if updated_file and (not is_dependency_issue or file_name in ['requirements.txt', 'Dockerfile']): @@ -280,7 +280,7 @@ complete file. Use the exact same syntax to wrap the code: print_colored('', 'Is it a dependency issue?', 'blue') conversation = self.gpt_session.get_conversation([]) - answer = conversation.query( + answer = conversation.chat( f'Your task is to assist in identifying the root cause of a Docker build error for a python application. ' f'The error message is as follows::\n\n{error}\n\n' f'The docker file is as follows:\n\n{docker_file}\n\n' @@ -305,7 +305,7 @@ The output is a the raw string wrapped into ``` and starting with **name.txt** l PDFParserExecutor ``` ''' - name_raw = conversation.query(user_query) + name_raw = conversation.chat(user_query) name = self.extract_content_from_result(name_raw, 'name.txt') return name @@ -341,7 +341,7 @@ package5 ``` ''' conversation = self.gpt_session.get_conversation() - packages_raw = conversation.query(user_query) + packages_raw = conversation.chat(user_query) packages_csv_string = self.extract_content_from_result(packages_raw, 'packages.csv') packages = [package.split(',') for package in packages_csv_string.split('\n')] packages = packages[:NUM_IMPLEMENTATION_STRATEGIES] @@ -351,13 +351,17 @@ package5 generated_name = self.generate_microservice_name(description) microservice_name = f'{generated_name}{random.randint(0, 10_000_000)}' packages_list = self.get_possible_packages(description) - packages_list = [packages for packages in packages_list if len(set(packages).intersection(set(PROBLEMATIC_PACKAGES))) == 0] + packages_list = [ + packages for packages in packages_list if len(set(packages).intersection(set(PROBLEMATIC_PACKAGES))) == 0 + ] for num_approach, packages in enumerate(packages_list): try: - self.generate_microservice(description, test, microservice_path, microservice_name, packages, - num_approach) - final_version_path = self.debug_microservice(microservice_path, microservice_name, num_approach, - packages, description, test) + self.generate_microservice( + description, test, microservice_path, microservice_name, packages, num_approach + ) + final_version_path = self.debug_microservice( + microservice_path, microservice_name, num_approach, packages, description, test + ) self.generate_playground(microservice_name, final_version_path) except self.MaxDebugTimeReachedException: print('Could not debug the Microservice with the approach:', packages) diff --git a/src/options/generate/prompt_system.py b/src/options/generate/prompt_system.py index 8b0c987..64ad82e 100644 --- a/src/options/generate/prompt_system.py +++ b/src/options/generate/prompt_system.py @@ -71,7 +71,7 @@ print(response[0].text) ```''' -system_base_definition = f''' +system_message_base = f''' It is the year 2021. You are a principal engineer working at Jina - an open source company. You accurately satisfy all of the user's requirements. diff --git a/src/utils/io.py b/src/utils/io.py index c5cf727..272c878 100644 --- a/src/utils/io.py +++ b/src/utils/io.py @@ -31,28 +31,6 @@ def get_all_microservice_files_with_content(folder_path): return file_name_to_content -class GenerationTimeoutError(Exception): - pass - -def timeout_generator_wrapper(generator, timeout): - def generator_func(): - for item in generator: - yield item - - def wrapper() -> Generator: - gen = generator_func() - while True: - try: - with concurrent.futures.ThreadPoolExecutor() as executor: - future = executor.submit(next, gen) - yield future.result(timeout=timeout) - except StopIteration: - break - except concurrent.futures.TimeoutError: - raise GenerationTimeoutError(f"Generation took too long") - - return wrapper() - @contextmanager def suppress_stdout(): original_stdout = sys.stdout From 03de77b58e3b1e82d84185a818a256315d354dd4 Mon Sep 17 00:00:00 2001 From: Joschka Braun Date: Mon, 17 Apr 2023 15:05:58 +0200 Subject: [PATCH 2/9] refactor: simplify gpt session --- src/apis/gpt.py | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/src/apis/gpt.py b/src/apis/gpt.py index eb0a582..f02a0a4 100644 --- a/src/apis/gpt.py +++ b/src/apis/gpt.py @@ -7,12 +7,7 @@ import openai from langchain.callbacks import CallbackManager from langchain.chat_models import ChatOpenAI from openai.error import RateLimitError -from langchain.schema import ( - AIMessage, - HumanMessage, - SystemMessage, - BaseMessage -) +from langchain.schema import AIMessage, HumanMessage, SystemMessage, BaseMessage from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler from src.options.generate.prompt_system import system_message_base, executor_example, docarray_example, client_example @@ -22,13 +17,7 @@ from src.utils.string_tools import print_colored class GPTSession: def __init__(self, model: str = 'gpt-4'): self.configure_openai_api_key() - if model == 'gpt-4' and self.is_gpt4_available(): - self.supported_model = 'gpt-4' - else: - if model == 'gpt-4': - print_colored('GPT-4 is not available. Using GPT-3.5-turbo instead.', 'yellow') - model = 'gpt-3.5-turbo' - self.supported_model = model + self.model_name = 'gpt-4' if model == 'gpt-4' and self.is_gpt4_available() else 'gpt-3.5-turbo' @staticmethod def configure_openai_api_key(): @@ -58,10 +47,11 @@ If you have updated it already, please restart your terminal. continue return True except openai.error.InvalidRequestError: + print_colored('GPT-4 is not available. Using GPT-3.5-turbo instead.', 'yellow') return False def get_conversation(self, system_definition_examples: List[str] = ['executor', 'docarray', 'client']): - return _GPTConversation(self.supported_model, system_definition_examples) + return _GPTConversation(self.model_name, system_definition_examples) class AssistantStreamingStdOutCallbackHandler(StreamingStdOutCallbackHandler): From ca6d1998970791ae7e874daa69462687f0a95dd8 Mon Sep 17 00:00:00 2001 From: Joschka Braun Date: Mon, 17 Apr 2023 18:58:40 +0200 Subject: [PATCH 3/9] refactor: use templates --- src/options/generate/generator.py | 176 +++++--------------------- src/options/generate/prompt_system.py | 5 +- src/options/generate/templates.py | 132 +++++++++++++++++++ 3 files changed, 169 insertions(+), 144 deletions(-) create mode 100644 src/options/generate/templates.py diff --git a/src/options/generate/generator.py b/src/options/generate/generator.py index 21cb508..d5246c6 100644 --- a/src/options/generate/generator.py +++ b/src/options/generate/generator.py @@ -9,6 +9,9 @@ from src.constants import FILE_AND_TAG_PAIRS, NUM_IMPLEMENTATION_STRATEGIES, MAX from src.options.generate.prompt_tasks import general_guidelines, executor_file_task, \ not_allowed_executor, chain_of_thought_optimization, test_executor_file_task, requirements_file_task, \ docker_file_task, not_allowed_docker +from src.options.generate.templates import template_generate_microservice_name, template_generate_possible_packages, \ + template_solve_code_issue, \ + template_solve_dependency_issue, template_is_dependency_issue, template_generate_playground from src.utils.io import persist_file, get_all_microservice_files_with_content, get_microservice_path from src.utils.string_tools import print_colored @@ -33,27 +36,24 @@ class Generator: def write_config_yml(self, microservice_name, dest_folder): config_content = f''' - jtype: {microservice_name} - py_modules: - - microservice.py - metas: - name: {microservice_name} - ''' +jtype: {microservice_name} +py_modules: + - microservice.py +metas: + name: {microservice_name} +''' with open(os.path.join(dest_folder, 'config.yml'), 'w') as f: f.write(config_content) - def files_to_string(self, file_name_to_content): + def files_to_string(self, file_name_to_content, restrict_keys=None): all_microservice_files_string = '' for file_name, tag in FILE_AND_TAG_PAIRS: - if file_name in file_name_to_content: - all_microservice_files_string += f'**{file_name}**\n' - all_microservice_files_string += f'```{tag}\n' - all_microservice_files_string += file_name_to_content[file_name] - all_microservice_files_string += '\n```' + if file_name in file_name_to_content and (not restrict_keys or file_name in restrict_keys): + all_microservice_files_string += f'**{file_name}**\n```{tag}\n{file_name_to_content[file_name]}\n```\n\n' return all_microservice_files_string - def wrap_content_in_code_block(self, microservice_content, file_name, tag): - return f'**{file_name}**\n```{tag}\n{microservice_content}\n```\n\n' + # def wrap_content_in_code_block(self, microservice_content, file_name, tag): + # return f'**{file_name}**\n```{tag}\n{microservice_content}\n```\n\n' def generate_microservice( self, @@ -149,30 +149,15 @@ class Generator: print_colored('', '############# Playground #############', 'blue') file_name_to_content = get_all_microservice_files_with_content(microservice_path) - user_query = ( - general_guidelines() - + self.wrap_content_in_code_block(file_name_to_content['microservice.py'], 'microservice.py', 'python') - + self.wrap_content_in_code_block(file_name_to_content['test_microservice.py'], 'test_microservice.py', - 'python') - + f''' -Create a playground for the executor {microservice_name} using streamlit. -The playground must look like it was made by a professional designer. -All the ui elements are well thought out to make them visually appealing and easy to use. -This is an example how you can connect to the executor assuming the document (d) is already defined: -``` -from jina import Client, Document, DocumentArray -client = Client(host=host) -response = client.post('/', inputs=DocumentArray([d])) # always use '/' -print(response[0].text) # can also be blob in case of image/audio..., this should be visualized in the streamlit app -``` -Note that the response will always be in response[0].text -You must provide the complete file with the exact same syntax to wrap the code. -The playground (app.py) must read the host from sys.argv because it will be started with a custom host: streamlit run app.py -- --host grpc://... -The playground (app.py) must not let the user configure the host on the ui. -''' - ) conversation = self.gpt_session.get_conversation([]) - conversation.chat(user_query) + conversation.chat( + template_generate_playground.format( + general_guidelines=general_guidelines(), + code_files_wrapped=self.files_to_string(file_name_to_content, ['microservice.py', 'test_microservice.py']), + microservice_name=microservice_name, + + ) + ) playground_content_raw = conversation.chat(chain_of_thought_optimization('python', 'app.py', 'the playground')) playground_content = self.extract_content_from_result(playground_content_raw, 'app.py', match_single_block=True) persist_file(playground_content, os.path.join(microservice_path, 'app.py')) @@ -208,10 +193,15 @@ The playground (app.py) must not let the user configure the host on the ui. key: val for key, val in file_name_to_content.items() if key in ['requirements.txt', 'Dockerfile'] }) - user_query = self.get_user_query_dependency_issue(all_files_string, error) + user_query = template_solve_dependency_issue.format( + description=description, error=error, all_files_string=all_files_string, + not_allowed_docker=not_allowed_docker() + ) else: - user_query = self.get_user_query_code_issue(description, error, file_name_to_content, - test) + user_query = template_solve_code_issue.format( + description=description, error=error, all_files_string=self.files_to_string(file_name_to_content), + not_allowed_executor=not_allowed_executor() + ) conversation = self.gpt_session.get_conversation() returned_files_raw = conversation.chat(user_query) for file_name, tag in FILE_AND_TAG_PAIRS: @@ -221,55 +211,6 @@ The playground (app.py) must not let the user configure the host on the ui. for file_name, content in file_name_to_content.items(): persist_file(content, os.path.join(next_microservice_path, file_name)) - def get_user_query_dependency_issue(self, all_files_string, error): - user_query = ( - f''' -Your task is to provide guidance on how to solve an error that occurred during the Docker build process. -The error message is: -**microservice.log** -``` -{error} -``` -To solve this error, you should: -1. Identify the type of error by examining the stack trace. -2. Suggest how to solve it. -3. Your suggestion must include the files that need to be changed, but not files that don't need to be changed. -For files that need to be changed, you must provide the complete file with the exact same syntax to wrap the code. -Obey the following rules: {not_allowed_docker()} - -You are given the following files: - -{all_files_string}" -''' - ) - return user_query - - def get_user_query_code_issue(self, description, error, file_name_to_content, test): - all_files_string = self.files_to_string(file_name_to_content) - return f''' -General rules: {not_allowed_executor()} -Here is the description of the task the executor must solve: -{description} - -Here is the test scenario the executor must pass:\n{test} -Here are all the files I use: -{all_files_string} - - -This is the error I encounter currently during the docker build process: -{error} - -Look at the stack trace of the current error. First, think about what kind of error is this? -Then think about possible reasons which might have caused it. Then suggest how to -solve it. Output all the files that need change. -Don't output files that don't need change. If you output a file, then write the -complete file. Use the exact same syntax to wrap the code: -**...** -```... -...code... -``` -''' - class MaxDebugTimeReachedException(BaseException): pass @@ -280,68 +221,21 @@ complete file. Use the exact same syntax to wrap the code: print_colored('', 'Is it a dependency issue?', 'blue') conversation = self.gpt_session.get_conversation([]) - answer = conversation.chat( - f'Your task is to assist in identifying the root cause of a Docker build error for a python application. ' - f'The error message is as follows::\n\n{error}\n\n' - f'The docker file is as follows:\n\n{docker_file}\n\n' - f'Is this a dependency installation failure? Answer with "yes" or "no".' - ) + answer = conversation.chat(template_is_dependency_issue.format(error=error, docker_file=docker_file)) return 'yes' in answer.lower() def generate_microservice_name(self, description): conversation = self.gpt_session.get_conversation() - user_query = f''' -Generate a name for the executor matching the description: -"{description}" -The executor name must fulfill the following criteria: -- camel case -- start with a capital letter -- only consists of lower and upper case characters -- end with Executor. - -The output is a the raw string wrapped into ``` and starting with **name.txt** like this: -**name.txt** -``` -PDFParserExecutor -``` -''' - name_raw = conversation.chat(user_query) + name_raw = conversation.chat(template_generate_microservice_name.format(description=description)) name = self.extract_content_from_result(name_raw, 'name.txt') return name def get_possible_packages(self, description): print_colored('', '############# What packages to use? #############', 'blue') - user_query = f''' -Here is the task description of the problem you need to solve: -"{description}" -1. Write down all the non-trivial subtasks you need to solve. -2. Find out what is the core problem to solve. -3. List up to 15 Python packages that are specifically designed or have functionalities to solve the complete core problem. -4. For each of the 15 package think if it fulfills the following requirements: -a) specifically designed or have functionalities to solve the complete core problem. -b) has a stable api among different versions -c) does not have system requirements -d) can solve the task when running in a docker container -e) the implementation of the core problem using the package would obey the following rules: -{not_allowed_executor()} -When answering, just write "yes" or "no". - -5. Output the most suitable 5 python packages starting with the best one. -If the package is mentioned in the description, then it is automatically the best one. - -The output must be a list of lists wrapped into ``` and starting with **packages.csv** like this: -**packages.csv** -``` -package1 -package2 -package3 -package4 -package5 -... -``` -''' conversation = self.gpt_session.get_conversation() - packages_raw = conversation.chat(user_query) + packages_raw = conversation.chat( + template_generate_possible_packages.format(description=description, not_allowed_executor=not_allowed_executor()) + ) packages_csv_string = self.extract_content_from_result(packages_raw, 'packages.csv') packages = [package.split(',') for package in packages_csv_string.split('\n')] packages = packages[:NUM_IMPLEMENTATION_STRATEGIES] diff --git a/src/options/generate/prompt_system.py b/src/options/generate/prompt_system.py index 64ad82e..d13b307 100644 --- a/src/options/generate/prompt_system.py +++ b/src/options/generate/prompt_system.py @@ -71,8 +71,7 @@ print(response[0].text) ```''' -system_message_base = f''' -It is the year 2021. +system_message_base = '''It is the year 2021. You are a principal engineer working at Jina - an open source company. You accurately satisfy all of the user's requirements. -''' \ No newline at end of file +Your goal is to build a microservice that: {description}''' \ No newline at end of file diff --git a/src/options/generate/templates.py b/src/options/generate/templates.py new file mode 100644 index 0000000..d334b3b --- /dev/null +++ b/src/options/generate/templates.py @@ -0,0 +1,132 @@ +from langchain import PromptTemplate + +template_generate_microservice_name = PromptTemplate.from_template( + '''Generate a name for the executor matching the description: +"{description}" +The executor name must fulfill the following criteria: +- camel case +- start with a capital letter +- only consists of lower and upper case characters +- end with Executor. + +The output is a the raw string wrapped into ``` and starting with **name.txt** like this: +**name.txt** +``` +PDFParserExecutor +```''' +) + + +template_generate_possible_packages = PromptTemplate.from_template( + '''Here is the task description of the problem you need to solve: +"{description}" +1. Write down all the non-trivial subtasks you need to solve. +2. Find out what is the core problem to solve. +3. List up to 15 Python packages that are specifically designed or have functionalities to solve the complete core problem. +4. For each of the 15 package think if it fulfills the following requirements: +a) specifically designed or have functionalities to solve the complete core problem. +b) has a stable api among different versions +c) does not have system requirements +d) can solve the task when running in a docker container +e) the implementation of the core problem using the package would obey the following rules: +{not_allowed_executor} +When answering, just write "yes" or "no". + +5. Output the most suitable 5 python packages starting with the best one. +If the package is mentioned in the description, then it is automatically the best one. + +The output must be a list of lists wrapped into ``` and starting with **packages.csv** like this: +**packages.csv** +``` +package1 +package2 +package3 +package4 +package5 +... +``` +''') + + +template_solve_code_issue = PromptTemplate.from_template( + '''General rules: {not_allowed_executor} +Here is the description of the task the executor must solve: +{description} + +Here is the test scenario the executor must pass:\n{test} +Here are all the files I use: +{all_files_string} + + +This is the error I encounter currently during the docker build process: +{error} + +Look at the stack trace of the current error. First, think about what kind of error is this? +Then think about possible reasons which might have caused it. Then suggest how to +solve it. Output all the files that need change. +Don't output files that don't need change. If you output a file, then write the +complete file. Use the exact same syntax to wrap the code: +**...** +```... +...code... +``` +''' +) + + +template_solve_dependency_issue = PromptTemplate.from_template( + '''Your task is to provide guidance on how to solve an error that occurred during the Docker build process. +The error message is: +**microservice.log** +``` +{error} +``` +To solve this error, you should: +1. Identify the type of error by examining the stack trace. +2. Suggest how to solve it. +3. Your suggestion must include the files that need to be changed, but not files that don't need to be changed. +For files that need to be changed, you must provide the complete file with the exact same syntax to wrap the code. +Obey the following rules: {not_allowed_docker} + +You are given the following files: + +{all_files_string} +''' +) + + +template_is_dependency_issue = PromptTemplate.from_template( + '''Your task is to assist in identifying the root cause of a Docker build error for a python application. +The error message is as follows: + +{error} + +The docker file is as follows: + +{docker_file} + +Is this a dependency installation failure? Answer with "yes" or "no".''' +) + + +template_generate_playground = PromptTemplate.from_template( + '''{general_guidelines} + +{code_files_wrapped} + +Create a playground for the executor {microservice_name} using streamlit. +The playground must look like it was made by a professional designer. +All the ui elements are well thought out to make them visually appealing and easy to use. +This is an example how you can connect to the executor assuming the document (d) is already defined: +``` +from jina import Client, Document, DocumentArray +client = Client(host=host) +response = client.post('/', inputs=DocumentArray([d])) # always use '/' +print(response[0].text) # can also be blob in case of image/audio..., this should be visualized in the streamlit app +``` +Note that the response will always be in response[0].text +You must provide the complete file with the exact same syntax to wrap the code. +The playground (app.py) must read the host from sys.argv because it will be started with a custom host: streamlit run app.py -- --host grpc://... +The playground (app.py) must not let the user configure the host on the ui. +''' +) From 86f50e1c868140f1f41eeb32ac3980a84895f612 Mon Sep 17 00:00:00 2001 From: Joschka Braun Date: Tue, 18 Apr 2023 12:00:10 +0200 Subject: [PATCH 4/9] refactor: use templates --- src/options/generate/generator.py | 121 +++++++++-------- src/options/generate/prompt_tasks.py | 151 --------------------- src/options/generate/templates.py | 196 +++++++++++++++++++++------ 3 files changed, 219 insertions(+), 249 deletions(-) delete mode 100644 src/options/generate/prompt_tasks.py diff --git a/src/options/generate/generator.py b/src/options/generate/generator.py index d5246c6..35139bf 100644 --- a/src/options/generate/generator.py +++ b/src/options/generate/generator.py @@ -5,13 +5,13 @@ import re from src.apis import gpt from src.apis.jina_cloud import process_error_message, push_executor from src.constants import FILE_AND_TAG_PAIRS, NUM_IMPLEMENTATION_STRATEGIES, MAX_DEBUGGING_ITERATIONS, \ - PROBLEMATIC_PACKAGES -from src.options.generate.prompt_tasks import general_guidelines, executor_file_task, \ - not_allowed_executor, chain_of_thought_optimization, test_executor_file_task, requirements_file_task, \ - docker_file_task, not_allowed_docker + PROBLEMATIC_PACKAGES, EXECUTOR_FILE_NAME, EXECUTOR_FILE_TAG, TEST_EXECUTOR_FILE_NAME, TEST_EXECUTOR_FILE_TAG, \ + REQUIREMENTS_FILE_NAME, REQUIREMENTS_FILE_TAG, DOCKER_FILE_NAME, DOCKER_FILE_TAG from src.options.generate.templates import template_generate_microservice_name, template_generate_possible_packages, \ template_solve_code_issue, \ - template_solve_dependency_issue, template_is_dependency_issue, template_generate_playground + template_solve_dependency_issue, template_is_dependency_issue, template_generate_playground, \ + template_generate_executor, template_generate_test, template_generate_requirements, template_generate_dockerfile, \ + template_chain_of_thought from src.utils.io import persist_file, get_all_microservice_files_with_content, get_microservice_path from src.utils.string_tools import print_colored @@ -52,9 +52,6 @@ metas: all_microservice_files_string += f'**{file_name}**\n```{tag}\n{file_name_to_content[file_name]}\n```\n\n' return all_microservice_files_string - # def wrap_content_in_code_block(self, microservice_content, file_name, tag): - # return f'**{file_name}**\n```{tag}\n{microservice_content}\n```\n\n' - def generate_microservice( self, description, @@ -63,24 +60,26 @@ metas: microservice_name, package, num_approach, - is_chain_of_thought=False, ): MICROSERVICE_FOLDER_v1 = get_microservice_path(path, microservice_name, package, num_approach, 1) os.makedirs(MICROSERVICE_FOLDER_v1) print_colored('', '############# Microservice #############', 'blue') - user_query = ( - general_guidelines() - + executor_file_task(microservice_name, description, test, package) - ) conversation = self.gpt_session.get_conversation() - microservice_content_raw = conversation.chat(user_query) - if is_chain_of_thought: - microservice_content_raw = conversation.chat( - f"General rules: " + not_allowed_executor() + chain_of_thought_optimization('python', - 'microservice.py')) - microservice_content = self.extract_content_from_result(microservice_content_raw, 'microservice.py', - match_single_block=True) + microservice_content_raw = conversation.chat( + template_generate_executor.format( + microservice_name=microservice_name, + microservice_description=description, + test=test, + package=package, + file_name_purpose=EXECUTOR_FILE_NAME, + tag_name=EXECUTOR_FILE_TAG, + file_name=EXECUTOR_FILE_NAME, + ) + ) + microservice_content = self.extract_content_from_result( + microservice_content_raw, 'microservice.py', match_single_block=True + ) if microservice_content == '': microservice_content_raw = conversation.chat('You must add the executor code.') microservice_content = self.extract_content_from_result( @@ -89,19 +88,16 @@ metas: persist_file(microservice_content, os.path.join(MICROSERVICE_FOLDER_v1, 'microservice.py')) print_colored('', '############# Test Microservice #############', 'blue') - user_query = ( - general_guidelines() - + self.wrap_content_in_code_block(microservice_content, 'microservice.py', 'python') - + test_executor_file_task(microservice_name, test) - ) conversation = self.gpt_session.get_conversation() - test_microservice_content_raw = conversation.chat(user_query) - if is_chain_of_thought: - test_microservice_content_raw = conversation.chat( - f"General rules: " + not_allowed_executor() + - chain_of_thought_optimization('python', 'test_microservice.py') - + "Don't add any additional tests. " + test_microservice_content_raw = conversation.chat( + template_generate_test.format( + code_files_wrapped=self.files_to_string({'microservice.py': microservice_content}), + microservice_name=microservice_name, + file_name_purpose=TEST_EXECUTOR_FILE_NAME, + tag_name=TEST_EXECUTOR_FILE_TAG, + file_name=TEST_EXECUTOR_FILE_NAME, ) + ) test_microservice_content = self.extract_content_from_result( test_microservice_content_raw, 'microservice.py', match_single_block=True ) @@ -109,37 +105,41 @@ metas: print_colored('', '############# Requirements #############', 'blue') requirements_path = os.path.join(MICROSERVICE_FOLDER_v1, 'requirements.txt') - user_query = ( - general_guidelines() - + self.wrap_content_in_code_block(microservice_content, 'microservice.py', 'python') - + self.wrap_content_in_code_block(test_microservice_content, 'test_microservice.py', 'python') - + requirements_file_task() - ) conversation = self.gpt_session.get_conversation() - requirements_content_raw = conversation.chat(user_query) - if is_chain_of_thought: - requirements_content_raw = conversation.chat( - chain_of_thought_optimization('', requirements_path) + "Keep the same version of jina ") + requirements_content_raw = conversation.chat( + template_generate_requirements.format( + code_files_wrapped=self.files_to_string( + {'microservice.py': microservice_content, 'test_microservice.py': test_microservice_content} + ), + file_name_purpose=REQUIREMENTS_FILE_NAME, + file_name=REQUIREMENTS_FILE_NAME, + tag_name=REQUIREMENTS_FILE_TAG, + ) + ) requirements_content = self.extract_content_from_result(requirements_content_raw, 'requirements.txt', match_single_block=True) persist_file(requirements_content, requirements_path) print_colored('', '############# Dockerfile #############', 'blue') - user_query = ( - general_guidelines() - + self.wrap_content_in_code_block(microservice_content, 'microservice.py', 'python') - + self.wrap_content_in_code_block(test_microservice_content, 'test_microservice.py', 'python') - + self.wrap_content_in_code_block(requirements_content, 'requirements.txt', '') - + docker_file_task() - ) conversation = self.gpt_session.get_conversation() - dockerfile_content_raw = conversation.chat(user_query) - if is_chain_of_thought: - dockerfile_content_raw = conversation.chat( - f"General rules: " + not_allowed_executor() + chain_of_thought_optimization('dockerfile', 'Dockerfile')) - dockerfile_content = self.extract_content_from_result(dockerfile_content_raw, 'Dockerfile', - match_single_block=True) + dockerfile_content_raw = conversation.chat( + template_generate_dockerfile.format( + code_files_wrapped=self.files_to_string( + { + 'microservice.py': microservice_content, + 'test_microservice.py': test_microservice_content, + 'requirements.txt': requirements_content, + } + ), + file_name_purpose=DOCKER_FILE_NAME, + file_name=DOCKER_FILE_NAME, + tag_name=DOCKER_FILE_TAG, + ) + ) + dockerfile_content = self.extract_content_from_result( + dockerfile_content_raw, 'Dockerfile', match_single_block=True + ) persist_file(dockerfile_content, os.path.join(MICROSERVICE_FOLDER_v1, 'Dockerfile')) self.write_config_yml(microservice_name, MICROSERVICE_FOLDER_v1) @@ -152,13 +152,18 @@ metas: conversation = self.gpt_session.get_conversation([]) conversation.chat( template_generate_playground.format( - general_guidelines=general_guidelines(), code_files_wrapped=self.files_to_string(file_name_to_content, ['microservice.py', 'test_microservice.py']), microservice_name=microservice_name, ) ) - playground_content_raw = conversation.chat(chain_of_thought_optimization('python', 'app.py', 'the playground')) + playground_content_raw = conversation.chat( + template_chain_of_thought.format( + file_name_purpose='app.py/the playground', + file_name='app.py', + tag_name='python', + ) + ) playground_content = self.extract_content_from_result(playground_content_raw, 'app.py', match_single_block=True) persist_file(playground_content, os.path.join(microservice_path, 'app.py')) @@ -195,12 +200,10 @@ metas: }) user_query = template_solve_dependency_issue.format( description=description, error=error, all_files_string=all_files_string, - not_allowed_docker=not_allowed_docker() ) else: user_query = template_solve_code_issue.format( description=description, error=error, all_files_string=self.files_to_string(file_name_to_content), - not_allowed_executor=not_allowed_executor() ) conversation = self.gpt_session.get_conversation() returned_files_raw = conversation.chat(user_query) @@ -234,7 +237,7 @@ metas: print_colored('', '############# What packages to use? #############', 'blue') conversation = self.gpt_session.get_conversation() packages_raw = conversation.chat( - template_generate_possible_packages.format(description=description, not_allowed_executor=not_allowed_executor()) + template_generate_possible_packages.format(description=description) ) packages_csv_string = self.extract_content_from_result(packages_raw, 'packages.csv') packages = [package.split(',') for package in packages_csv_string.split('\n')] diff --git a/src/options/generate/prompt_tasks.py b/src/options/generate/prompt_tasks.py deleted file mode 100644 index 0c13b73..0000000 --- a/src/options/generate/prompt_tasks.py +++ /dev/null @@ -1,151 +0,0 @@ -from src.constants import EXECUTOR_FILE_NAME, REQUIREMENTS_FILE_NAME, TEST_EXECUTOR_FILE_NAME, DOCKER_FILE_NAME, \ - DOCKER_FILE_TAG, CLIENT_FILE_TAG, CLIENT_FILE_NAME, STREAMLIT_FILE_TAG, STREAMLIT_FILE_NAME, EXECUTOR_FILE_TAG, \ - REQUIREMENTS_FILE_TAG, TEST_EXECUTOR_FILE_TAG - - -def general_guidelines(): - return ( - "The code you write is production ready. " - "Every file starts with comments describing what the code is doing before the first import. " - "Comments can only be written within code blocks. " - "Then all imports are listed. " - "It is important to import all modules that could be needed in the Executor code. " - "Always import: " - "from jina import Executor, DocumentArray, Document, requests " - "Start from top-level and then fully implement all methods. " - "\n" - ) - - -def _task(task, tag_name, file_name, purpose=None): - into_string = file_name - if purpose: - into_string += f"/{purpose}" - - return ( - task + f"The code will go into {into_string}. Make sure to wrap the code into ``` marks even if you only " - f"output code:\n" - f"**{file_name}**\n" - f"```{tag_name}\n" - f"...code...\n" - f"```\nYou must provide the complete file with the exact same syntax to wrap the code." - ) - - -def executor_file_task(executor_name, executor_description, test_scenario, package): - return _task(f''' -Write the executor called '{executor_name}'. The name is very important to keep. -It matches the following description: '{executor_description}'. -It will be tested with the following scenario: '{test_scenario}'. -For the implementation use the following package: '{package}'. - -Obey the following rules: -Have in mind that d.uri is never a path to a local file. It is always a url. -{not_allowed_executor()} -Your approach: -1. Identify the core challenge when implementing the executor. -2. Think about solutions for these challenges. -3. Decide for one of the solutions. -4. Write the code. -''', EXECUTOR_FILE_TAG, EXECUTOR_FILE_NAME) - - -def test_executor_file_task(executor_name, test_scenario): - return _task( - "Write a small unit test for the executor. " - "Start the test with an extensive comment about the test case. " - + ( - f"Write a single test case that tests the following scenario: '{test_scenario}'. " - f"In case the test scenario is not precise enough, test a general case without any assumptions." - if test_scenario else "" - ) - + "Use the following import to import the executor: " - f"```\nfrom microservice import {executor_name}\n```" - + not_allowed_executor() - + "The test must not open local files. " - + "The test must not mock a function of the executor. " - + "The test must not use other data than the one provided in the test scenario. ", - TEST_EXECUTOR_FILE_TAG, - TEST_EXECUTOR_FILE_NAME - ) - -def requirements_file_task(): - return _task( - "Write the content of the requirements.txt file. " - "Make sure to include pytest. " - "Make sure that jina==3.14.1. " - "All versions are fixed using ~=, ==, <, >, <=, >=. The package versions should not have conflicts. ", - REQUIREMENTS_FILE_TAG, - REQUIREMENTS_FILE_NAME - ) - - -def docker_file_task(): - return _task( - "Write the Dockerfile that defines the environment with all necessary dependencies that the executor uses. " - "It is important to make sure that all libs are installed that are required by the python packages. " - "Usually libraries are installed with apt-get. " - "Be aware that the machine the docker container is running on does not have a GPU - only CPU. " - "Add the config.yml file to the Dockerfile. Note that the Dockerfile only has access to the files: " - "microservice.py, requirements.txt, config.yml, test_microservice.py. " - "The base image of the Dockerfile is FROM jinaai/jina:3.14.1-py39-standard. " - 'The entrypoint is ENTRYPOINT ["jina", "executor", "--uses", "config.yml"]. ' - 'Make sure the all files are in the /workdir. ' - "The Dockerfile runs the test during the build process. " + not_allowed_docker(), - DOCKER_FILE_TAG, - DOCKER_FILE_NAME - ) - - -def client_file_task(): - return _task( - "Write the client file. ", - CLIENT_FILE_TAG, - CLIENT_FILE_NAME - ) - - -def streamlit_file_task(): - return _task( - "Write the streamlit file allowing to make requests . ", - STREAMLIT_FILE_TAG, - STREAMLIT_FILE_NAME - ) - -def chain_of_thought_optimization(tag_name, file_name, file_name_function=None): - file_name_or_function = file_name - if file_name_function: - file_name_or_function += f"/{file_name_function}" - return _task( - f'First, write down an extensive list of obvious and non-obvious observations about {file_name_or_function} that could need an adjustment. Explain why. ' - f"Think if all the changes are required and finally decide for the changes you want to make, " - f"but you are not allowed disregard the instructions in the previous message. " - f"Be very hesitant to change the code. Only make a change if you are sure that it is necessary. " - - f"Output only {file_name_or_function} " - f"Write the whole content of {file_name_or_function} - even if you decided to change only a small thing or even nothing. ", - tag_name, - file_name, - file_name_function - ) - -def not_allowed_executor(): - return ''' -The executor and the test must not use the GPU. -The executor and the test must not access a database. -The executor and the test must not access a display. -The executor and the test must not access external apis except unless it is explicitly mentioned in the description or test case (e.g. by mentioning the api that should be used or by providing a URL to access the data). -The executor and the test must not load data from the local file system unless it was created by the executor itself. -The executor and the test must not use a pre-trained model unless it is explicitly mentioned in the description. -The executor and the test must not train a model. -The executor and the test must not use any attribute of Document accept Document.text. -The executor and the test must not contain prototype or placeholder implementations. -The executor and the test must run in a docker container based on debian. -''' - -def not_allowed_docker(): - return ''' -Note that the Dockerfile only has access to the files: microservice.py, requirements.txt, config.yml, test_microservice.py. -Note that the Dockerfile runs the test_microservice.py during the build process. -The Dockerfile must not attach a virtual display when running test_microservice.py. -''' diff --git a/src/options/generate/templates.py b/src/options/generate/templates.py index d334b3b..789f898 100644 --- a/src/options/generate/templates.py +++ b/src/options/generate/templates.py @@ -1,5 +1,28 @@ from langchain import PromptTemplate + +general_guidelines_string = '''The code you write is production ready. Every file starts with comments describing what the code is doing before the first import. Comments can only be written within code blocks. +Then all imports are listed. It is important to import all modules that could be needed in the Executor code. Always import: from jina import Executor, DocumentArray, Document, requests +Start from top-level and then fully implement all methods.''' + + +not_allowed_docker_string = '''Note that the Dockerfile only has access to the files: microservice.py, requirements.txt, config.yml, test_microservice.py. +Note that the Dockerfile runs the test_microservice.py during the build process. +The Dockerfile must not attach a virtual display when running test_microservice.py.''' + + +not_allowed_executor_string = '''The executor and the test must not use the GPU. +The executor and the test must not access a database. +The executor and the test must not access a display. +The executor and the test must not access external apis except unless it is explicitly mentioned in the description or test case (e.g. by mentioning the api that should be used or by providing a URL to access the data). +The executor and the test must not load data from the local file system unless it was created by the executor itself. +The executor and the test must not use a pre-trained model unless it is explicitly mentioned in the description. +The executor and the test must not train a model. +The executor and the test must not use any attribute of Document accept Document.text. +The executor and the test must not contain prototype or placeholder implementations. +The executor and the test must run in a docker container based on debian.''' + + template_generate_microservice_name = PromptTemplate.from_template( '''Generate a name for the executor matching the description: "{description}" @@ -29,7 +52,7 @@ b) has a stable api among different versions c) does not have system requirements d) can solve the task when running in a docker container e) the implementation of the core problem using the package would obey the following rules: -{not_allowed_executor} +''' + not_allowed_executor_string() + ''' When answering, just write "yes" or "no". 5. Output the most suitable 5 python packages starting with the best one. @@ -48,12 +71,131 @@ package5 ''') +template_code_wrapping_string = '''The code will go into {file_name_purpose}. Make sure to wrap the code into ``` marks even if you only output code: +**{file_name}** +```{tag_name} +...code... +``` +You must provide the complete file with the exact same syntax to wrap the code.''' + + +template_generate_executor = PromptTemplate.from_template( + general_guidelines_string + ''' + +Write the executor called '{microservice_name}'. The name is very important to keep. +It matches the following description: '{microservice_description}'. +It will be tested with the following scenario: '{test}'. +For the implementation use the following package: '{package}'. + +Obey the following rules: +Have in mind that d.uri is never a path to a local file. It is always a url. +''' + not_allowed_executor_string() + ''' + +Your approach: +1. Identify the core challenge when implementing the executor. +2. Think about solutions for these challenges. +3. Decide for one of the solutions. +4. Write the code. +''' + '\n' + template_code_wrapping_string +) + + +template_generate_test = PromptTemplate.from_template( + general_guidelines_string + ''' + +{code_files_wrapped} + +Write a single test case that tests the following scenario: '{test}'. In case the test scenario is not precise enough, test a general case without any assumptions. +Start the test with an extensive comment about the test case. + +Use the following import to import the executor: +``` +from microservice import {microservice_name} +``` + +''' + not_allowed_executor_string() + ''' +The test must not open local files. +The test must not mock a function of the executor. +The test must not use other data than the one provided in the test scenario. +''' + '\n' + template_code_wrapping_string +) + + +template_generate_requirements = PromptTemplate.from_template( + general_guidelines_string + ''' + +{code_files_wrapped} + +Write the content of the requirements.txt file. Make sure to include pytest. Make sure that jina==3.14.1. +All versions are fixed using ~=, ==, <, >, <=, >=. The package versions must not have conflicts. +''' + '\n' + template_code_wrapping_string +) + + +template_generate_dockerfile = PromptTemplate.from_template( + general_guidelines_string + ''' + +{code_files_wrapped} + +Write the Dockerfile that defines the environment with all necessary dependencies that the executor uses. +It is important to make sure that all libs are installed that are required by the python packages. +Usually libraries are installed with apt-get. +Be aware that the machine the docker container is running on does not have a GPU - only CPU. +Add the config.yml file to the Dockerfile. +Note that the Dockerfile only has access to the files: microservice.py, requirements.txt, config.yml, test_microservice.py. +The base image of the Dockerfile is FROM jinaai/jina:3.14.1-py39-standard. +The entrypoint is ENTRYPOINT ["jina", "executor", "--uses", "config.yml"]. +Make sure the all files are in the /workdir. +The Dockerfile runs the test during the build process. +''' + not_allowed_docker_string() + '\n' + template_code_wrapping_string +) + + +template_is_dependency_issue = PromptTemplate.from_template( + '''Your task is to assist in identifying the root cause of a Docker build error for a python application. +The error message is as follows: + +{error} + +The docker file is as follows: + +{docker_file} + +Is this a dependency installation failure? Answer with "yes" or "no".''' +) + + +template_solve_dependency_issue = PromptTemplate.from_template( + '''Your task is to provide guidance on how to solve an error that occurred during the Docker build process. +The error message is: +**microservice.log** +``` +{error} +``` +To solve this error, you should: +1. Identify the type of error by examining the stack trace. +2. Suggest how to solve it. +3. Your suggestion must include the files that need to be changed, but not files that don't need to be changed. +For files that need to be changed, you must provide the complete file with the exact same syntax to wrap the code. +Obey the following rules: +''' + not_allowed_docker_string() + ''' + +You are given the following files: + +{all_files_string} +''' +) + + template_solve_code_issue = PromptTemplate.from_template( - '''General rules: {not_allowed_executor} + '''General rules: +''' + not_allowed_executor_string() + ''' + Here is the description of the task the executor must solve: {description} -Here is the test scenario the executor must pass:\n{test} +Here is the test scenario the executor must pass: +{test} Here are all the files I use: {all_files_string} @@ -74,43 +216,8 @@ complete file. Use the exact same syntax to wrap the code: ) -template_solve_dependency_issue = PromptTemplate.from_template( - '''Your task is to provide guidance on how to solve an error that occurred during the Docker build process. -The error message is: -**microservice.log** -``` -{error} -``` -To solve this error, you should: -1. Identify the type of error by examining the stack trace. -2. Suggest how to solve it. -3. Your suggestion must include the files that need to be changed, but not files that don't need to be changed. -For files that need to be changed, you must provide the complete file with the exact same syntax to wrap the code. -Obey the following rules: {not_allowed_docker} - -You are given the following files: - -{all_files_string} -''' -) - - -template_is_dependency_issue = PromptTemplate.from_template( - '''Your task is to assist in identifying the root cause of a Docker build error for a python application. -The error message is as follows: - -{error} - -The docker file is as follows: - -{docker_file} - -Is this a dependency installation failure? Answer with "yes" or "no".''' -) - - template_generate_playground = PromptTemplate.from_template( - '''{general_guidelines} + general_guidelines_string + ''' {code_files_wrapped} @@ -130,3 +237,14 @@ The playground (app.py) must read the host from sys.argv because it will be star The playground (app.py) must not let the user configure the host on the ui. ''' ) + + +template_chain_of_thought = PromptTemplate.from_template( + '''First, write down an extensive list of obvious and non-obvious observations about {file_name_purpose} that could need an adjustment. Explain why. +Think if all the changes are required and finally decide for the changes you want to make, but you are not allowed disregard the instructions in the previous message. +Be very hesitant to change the code. Only make a change if you are sure that it is necessary. + +Output only {file_name_purpose} +Write the whole content of {file_name_purpose} - even if you decided to change only a small thing or even nothing. +''' + '\n' + template_code_wrapping_string +) From a63c9e39dbe90117f7aa38995568cab263852766 Mon Sep 17 00:00:00 2001 From: Joschka Braun Date: Tue, 18 Apr 2023 13:50:18 +0200 Subject: [PATCH 5/9] refactor: update general guidelines and req --- src/options/generate/prompt_tasks.py | 0 src/options/generate/templates.py | 9 +++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) delete mode 100644 src/options/generate/prompt_tasks.py diff --git a/src/options/generate/prompt_tasks.py b/src/options/generate/prompt_tasks.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/options/generate/templates.py b/src/options/generate/templates.py index 789f898..2104622 100644 --- a/src/options/generate/templates.py +++ b/src/options/generate/templates.py @@ -2,7 +2,12 @@ from langchain import PromptTemplate general_guidelines_string = '''The code you write is production ready. Every file starts with comments describing what the code is doing before the first import. Comments can only be written within code blocks. -Then all imports are listed. It is important to import all modules that could be needed in the Executor code. Always import: from jina import Executor, DocumentArray, Document, requests +Then all imports are listed. It is important to import all modules that could be needed in the Executor code. Always import: +from jina import Executor, DocumentArray, Document, requests +import json +from io import BytesIO +import requests as req + Start from top-level and then fully implement all methods.''' @@ -126,7 +131,7 @@ template_generate_requirements = PromptTemplate.from_template( {code_files_wrapped} -Write the content of the requirements.txt file. Make sure to include pytest. Make sure that jina==3.14.1. +Write the content of the requirements.txt file. Make sure to include pytest. Make sure that jina==3.14.1. Make sure that docarray==0.21.0. All versions are fixed using ~=, ==, <, >, <=, >=. The package versions must not have conflicts. ''' + '\n' + template_code_wrapping_string ) From 0692976088cc6041957638f193ac690da452a7f2 Mon Sep 17 00:00:00 2001 From: Joschka Braun Date: Tue, 18 Apr 2023 14:05:36 +0200 Subject: [PATCH 6/9] refactor: merge main --- src/apis/gpt.py | 7 ++- src/options/generate/generator.py | 42 +++++--------- src/options/generate/prompt_system.py | 3 +- src/options/generate/templates.py | 81 +++++++++++++++++++-------- 4 files changed, 80 insertions(+), 53 deletions(-) diff --git a/src/apis/gpt.py b/src/apis/gpt.py index 72dbc3c..1dd2cf7 100644 --- a/src/apis/gpt.py +++ b/src/apis/gpt.py @@ -4,6 +4,7 @@ from time import sleep from typing import List, Any import openai +from langchain import PromptTemplate from langchain.callbacks import CallbackManager from langchain.chat_models import ChatOpenAI from openai.error import RateLimitError @@ -88,12 +89,14 @@ class _GPTConversation: @staticmethod def _create_system_message(task_description, test_description, system_definition_examples: List[str] = []) -> SystemMessage: - system_message = system_message_base + system_message = PromptTemplate.from_template(system_message_base).format( + task_description=task_description, + test_description=test_description, + ) if 'executor' in system_definition_examples: system_message += f'\n{executor_example}' if 'docarray' in system_definition_examples: system_message += f'\n{docarray_example}' if 'client' in system_definition_examples: system_message += f'\n{client_example}' - # create from template return SystemMessage(content=system_message) diff --git a/src/options/generate/generator.py b/src/options/generate/generator.py index 3b5ab9d..cd6694f 100644 --- a/src/options/generate/generator.py +++ b/src/options/generate/generator.py @@ -11,7 +11,7 @@ from src.options.generate.templates import template_generate_microservice_name, template_solve_code_issue, \ template_solve_dependency_issue, template_is_dependency_issue, template_generate_playground, \ template_generate_executor, template_generate_test, template_generate_requirements, template_generate_dockerfile, \ - template_chain_of_thought + template_chain_of_thought, template_summarize_error from src.utils.io import persist_file, get_all_microservice_files_with_content, get_microservice_path from src.utils.string_tools import print_colored @@ -55,8 +55,6 @@ metas: def generate_microservice( self, - description, - test, path, microservice_name, packages, @@ -70,9 +68,9 @@ metas: microservice_content_raw = conversation.chat( template_generate_executor.format( microservice_name=microservice_name, - microservice_description=description, - test=test, - package=package, + microservice_description=self.task_description, + test=self.test_description, + packages=packages, file_name_purpose=EXECUTOR_FILE_NAME, tag_name=EXECUTOR_FILE_TAG, file_name=EXECUTOR_FILE_NAME, @@ -168,7 +166,7 @@ metas: playground_content = self.extract_content_from_result(playground_content_raw, 'app.py', match_single_block=True) persist_file(playground_content, os.path.join(microservice_path, 'app.py')) - def debug_microservice(self, path, microservice_name, num_approach, packages, description, test): + def debug_microservice(self, path, microservice_name, num_approach, packages): for i in range(1, MAX_DEBUGGING_ITERATIONS): print('Debugging iteration', i) print('Trying to build the microservice. Might take a while...') @@ -178,8 +176,7 @@ metas: error = process_error_message(log_hubble) if error: print('An error occurred during the build process. Feeding the error back to the assistent...') - self.do_debug_iteration(description, error, next_microservice_path, - previous_microservice_path, test) + self.do_debug_iteration(error, next_microservice_path, previous_microservice_path) if i == MAX_DEBUGGING_ITERATIONS - 1: raise self.MaxDebugTimeReachedException('Could not debug the microservice.') else: @@ -188,8 +185,7 @@ metas: return get_microservice_path(path, microservice_name, packages, num_approach, i) - def do_debug_iteration(self, description, error, next_microservice_path, previous_microservice_path, - test): + def do_debug_iteration(self, error, next_microservice_path, previous_microservice_path): os.makedirs(next_microservice_path) file_name_to_content = get_all_microservice_files_with_content(previous_microservice_path) @@ -201,11 +197,11 @@ metas: key in ['requirements.txt', 'Dockerfile'] }) user_query = template_solve_dependency_issue.format( - description=description, summarized_error=summarized_error, all_files_string=all_files_string, + description=self.task_description, summarized_error=summarized_error, all_files_string=all_files_string, ) else: user_query = template_solve_code_issue.format( - description=description, summarized_error=summarized_error, all_files_string=self.files_to_string(file_name_to_content), + description=self.task_description, summarized_error=summarized_error, all_files_string=self.files_to_string(file_name_to_content), ) conversation = self.gpt_session.get_conversation() returned_files_raw = conversation.chat(user_query) @@ -236,11 +232,11 @@ metas: name = self.extract_content_from_result(name_raw, 'name.txt') return name - def get_possible_packages(self, description): + def get_possible_packages(self): print_colored('', '############# What packages to use? #############', 'blue') conversation = self.gpt_session.get_conversation() packages_raw = conversation.chat( - template_generate_possible_packages.format(description=description) + template_generate_possible_packages.format(description=self.task_description) ) packages_csv_string = self.extract_content_from_result(packages_raw, 'packages.csv') packages_list = [[pkg.strip() for pkg in packages_string.split(',')] for packages_string in packages_csv_string.split('\n')] @@ -250,17 +246,15 @@ metas: def generate(self, microservice_path): generated_name = self.generate_microservice_name(self.task_description) microservice_name = f'{generated_name}{random.randint(0, 10_000_000)}' - packages_list = self.get_possible_packages(description) + packages_list = self.get_possible_packages() packages_list = [ packages for packages in packages_list if len(set(packages).intersection(set(PROBLEMATIC_PACKAGES))) == 0 ] for num_approach, packages in enumerate(packages_list): try: - self.generate_microservice( - self.task_description, self.test_description, microservice_path, microservice_name, packages, num_approach - ) + self.generate_microservice(microservice_path, microservice_name, packages, num_approach) final_version_path = self.debug_microservice( - microservice_path, microservice_name, num_approach, packages, description, test + microservice_path, microservice_name, num_approach, packages ) self.generate_playground(microservice_name, final_version_path) except self.MaxDebugTimeReachedException: @@ -280,11 +274,5 @@ gptdeploy deploy --path {microservice_path} def summarize_error(self, error): conversation = self.gpt_session.get_conversation([]) - user_query = f''' -Here is an error message I encountered during the docker build process: -"{error}" -Your task is to summarize the error message as compact and informative as possible while maintaining all information necessary to debug the core issue. -Warnings are not worth mentioning. -''' - error_summary = conversation.query(user_query) + error_summary = conversation.chat(template_summarize_error.format(error=error)) return error_summary diff --git a/src/options/generate/prompt_system.py b/src/options/generate/prompt_system.py index 801b5f3..42ced44 100644 --- a/src/options/generate/prompt_system.py +++ b/src/options/generate/prompt_system.py @@ -1,4 +1,5 @@ from src.constants import FLOW_URL_PLACEHOLDER +from src.options.generate.templates import not_allowed_docker_string, not_allowed_executor_string executor_example = '''Using the Jina framework, users can define executors. Here is an example of how an executor can be defined. It always starts with a comment: @@ -83,4 +84,4 @@ and the following test scenario: {test_description} ``` -You must obey the following rules:''' + f'\n{not_allowed_executor}\n{not_allowed_docker}' +You must obey the following rules:''' + f'\n{not_allowed_executor_string}\n{not_allowed_docker_string}' diff --git a/src/options/generate/templates.py b/src/options/generate/templates.py index 2104622..6447e6d 100644 --- a/src/options/generate/templates.py +++ b/src/options/generate/templates.py @@ -57,7 +57,8 @@ b) has a stable api among different versions c) does not have system requirements d) can solve the task when running in a docker container e) the implementation of the core problem using the package would obey the following rules: -''' + not_allowed_executor_string() + ''' +''' + not_allowed_executor_string + ''' + When answering, just write "yes" or "no". 5. Output the most suitable 5 python packages starting with the best one. @@ -66,11 +67,11 @@ If the package is mentioned in the description, then it is automatically the bes The output must be a list of lists wrapped into ``` and starting with **packages.csv** like this: **packages.csv** ``` -package1 -package2 -package3 -package4 -package5 +package1a, package1b ... +package2a, package2b, package2c +package3a ... +package4a ... +package5a ... ... ``` ''') @@ -90,7 +91,7 @@ template_generate_executor = PromptTemplate.from_template( Write the executor called '{microservice_name}'. The name is very important to keep. It matches the following description: '{microservice_description}'. It will be tested with the following scenario: '{test}'. -For the implementation use the following package: '{package}'. +For the implementation use the following package: '{packages}'. Obey the following rules: Have in mind that d.uri is never a path to a local file. It is always a url. @@ -156,6 +157,14 @@ The Dockerfile runs the test during the build process. ) +template_summarize_error = PromptTemplate.from_template( + '''Here is an error message I encountered during the docker build process: +"{error}" +Your task is to summarize the error message as compact and informative as possible while maintaining all information necessary to debug the core issue. +Warnings are not worth mentioning.''' +) + + template_is_dependency_issue = PromptTemplate.from_template( '''Your task is to assist in identifying the root cause of a Docker build error for a python application. The error message is as follows: @@ -172,15 +181,13 @@ Is this a dependency installation failure? Answer with "yes" or "no".''' template_solve_dependency_issue = PromptTemplate.from_template( '''Your task is to provide guidance on how to solve an error that occurred during the Docker build process. -The error message is: -**microservice.log** -``` -{error} -``` +Here is the summary of the error that occurred: +{summarized_error} + To solve this error, you should: -1. Identify the type of error by examining the stack trace. -2. Suggest how to solve it. -3. Your suggestion must include the files that need to be changed, but not files that don't need to be changed. +1. Suggest 3 to 5 possible solutions on how to solve it. You have no access to the documentation of the package. +2. Decide for the best solution and explain it in detail. +3. Write down the files that need to be changed, but not files that don't need to be changed. For files that need to be changed, you must provide the complete file with the exact same syntax to wrap the code. Obey the following rules: ''' + not_allowed_docker_string() + ''' @@ -188,6 +195,22 @@ Obey the following rules: You are given the following files: {all_files_string} + +Output all the files that need change. +Don't output files that don't need change. If you output a file, then write the +complete file. Use the exact following syntax to wrap the code: + +**...** +``` +...code... +``` + +Example: + +**requirements.txt** +``` +jina==2.0.0 +``` ''' ) @@ -205,19 +228,31 @@ Here are all the files I use: {all_files_string} -This is the error I encounter currently during the docker build process: -{error} +Here is the summary of the error that occurred: +{summarized_error} -Look at the stack trace of the current error. First, think about what kind of error is this? -Then think about possible reasons which might have caused it. Then suggest how to -solve it. Output all the files that need change. +To solve this error, you should: +1. Suggest 3 to 5 possible solutions on how to solve it. You have no access to the documentation of the package. +2. Decide for the best solution and explain it in detail. +3. Write down the files that need to be changed, but not files that don't need to be changed. +Obey the following rules: +''' + f'{not_allowed_executor_string}\n{not_allowed_docker_string}' + ''' + +Output all the files that need change. Don't output files that don't need change. If you output a file, then write the -complete file. Use the exact same syntax to wrap the code: +complete file. Use the exact following syntax to wrap the code: + **...** ```... ...code... ``` -''' + +Example: + +**microservice.py** +```python +print('hello world') +```''' ) @@ -237,7 +272,7 @@ response = client.post('/', inputs=DocumentArray([d])) # always use '/' print(response[0].text) # can also be blob in case of image/audio..., this should be visualized in the streamlit app ``` Note that the response will always be in response[0].text -You must provide the complete file with the exact same syntax to wrap the code. +You must provide the complete app.py file with the exact same syntax to wrap the code. The playground (app.py) must read the host from sys.argv because it will be started with a custom host: streamlit run app.py -- --host grpc://... The playground (app.py) must not let the user configure the host on the ui. ''' From 8a87cb3d410a9f1e20d195e77ceb03ab84574788 Mon Sep 17 00:00:00 2001 From: Joschka Braun Date: Tue, 18 Apr 2023 14:37:50 +0200 Subject: [PATCH 7/9] fix: fix typos --- src/apis/gpt.py | 16 ++++++++-------- src/options/generate/generator.py | 17 +++++++++-------- src/options/generate/prompt_system.py | 2 +- src/options/generate/templates.py | 12 ++++++------ 4 files changed, 24 insertions(+), 23 deletions(-) diff --git a/src/apis/gpt.py b/src/apis/gpt.py index 1dd2cf7..59d1156 100644 --- a/src/apis/gpt.py +++ b/src/apis/gpt.py @@ -60,20 +60,20 @@ If you have updated it already, please restart your terminal. class AssistantStreamingStdOutCallbackHandler(StreamingStdOutCallbackHandler): def on_llm_new_token(self, token: str, **kwargs: Any) -> None: """Run on new LLM token. Only available when streaming is enabled.""" - if os.environ['VERBOSE'].lower() == 'true': - print_colored('', token, 'green', end='') + print_colored('', token, 'green', end='') class _GPTConversation: def __init__(self, model: str, task_description, test_description, system_definition_examples: List[str] = ['executor', 'docarray', 'client']): - self.chat = ChatOpenAI( + self._chat = ChatOpenAI( model_name=model, streaming=True, callback_manager=CallbackManager([AssistantStreamingStdOutCallbackHandler()]), - temperature=0 + verbose=os.environ['VERBOSE'].lower() == 'true', + temperature=0, ) self.messages: List[BaseMessage] = [] - self.system_message = self._create_system_message(system_definition_examples) + self.system_message = self._create_system_message(task_description, test_description, system_definition_examples) if os.environ['VERBOSE'].lower() == 'true': print_colored('system', self.system_message.content, 'magenta') @@ -83,9 +83,9 @@ class _GPTConversation: if os.environ['VERBOSE'].lower() == 'true': print_colored('user', prompt, 'blue') print_colored('assistant', '', 'green', end='') - response = self.chat([self.system_message] + self.messages) - self.messages.append(AIMessage(content=response)) - return response + response = self._chat([self.system_message] + self.messages) + self.messages.append(response) + return response.content @staticmethod def _create_system_message(task_description, test_description, system_definition_examples: List[str] = []) -> SystemMessage: diff --git a/src/options/generate/generator.py b/src/options/generate/generator.py index cd6694f..6414a94 100644 --- a/src/options/generate/generator.py +++ b/src/options/generate/generator.py @@ -51,7 +51,7 @@ metas: for file_name, tag in FILE_AND_TAG_PAIRS: if file_name in file_name_to_content and (not restrict_keys or file_name in restrict_keys): all_microservice_files_string += f'**{file_name}**\n```{tag}\n{file_name_to_content[file_name]}\n```\n\n' - return all_microservice_files_string + return all_microservice_files_string.strip() def generate_microservice( self, @@ -63,7 +63,7 @@ metas: MICROSERVICE_FOLDER_v1 = get_microservice_path(path, microservice_name, packages, num_approach, 1) os.makedirs(MICROSERVICE_FOLDER_v1) - print_colored('', '############# Microservice #############', 'blue') + print_colored('', '\n############# Microservice #############', 'blue') conversation = self.gpt_session.get_conversation() microservice_content_raw = conversation.chat( template_generate_executor.format( @@ -86,12 +86,13 @@ metas: ) persist_file(microservice_content, os.path.join(MICROSERVICE_FOLDER_v1, 'microservice.py')) - print_colored('', '############# Test Microservice #############', 'blue') + print_colored('', '\n############# Test Microservice #############', 'blue') conversation = self.gpt_session.get_conversation() test_microservice_content_raw = conversation.chat( template_generate_test.format( code_files_wrapped=self.files_to_string({'microservice.py': microservice_content}), microservice_name=microservice_name, + test_description=self.test_description, file_name_purpose=TEST_EXECUTOR_FILE_NAME, tag_name=TEST_EXECUTOR_FILE_TAG, file_name=TEST_EXECUTOR_FILE_NAME, @@ -102,7 +103,7 @@ metas: ) persist_file(test_microservice_content, os.path.join(MICROSERVICE_FOLDER_v1, 'test_microservice.py')) - print_colored('', '############# Requirements #############', 'blue') + print_colored('', '\n############# Requirements #############', 'blue') requirements_path = os.path.join(MICROSERVICE_FOLDER_v1, 'requirements.txt') conversation = self.gpt_session.get_conversation() requirements_content_raw = conversation.chat( @@ -120,7 +121,7 @@ metas: match_single_block=True) persist_file(requirements_content, requirements_path) - print_colored('', '############# Dockerfile #############', 'blue') + print_colored('', '\n############# Dockerfile #############', 'blue') conversation = self.gpt_session.get_conversation() dockerfile_content_raw = conversation.chat( template_generate_dockerfile.format( @@ -142,10 +143,10 @@ metas: persist_file(dockerfile_content, os.path.join(MICROSERVICE_FOLDER_v1, 'Dockerfile')) self.write_config_yml(microservice_name, MICROSERVICE_FOLDER_v1) - print('First version of the microservice generated. Start iterating on it to make the tests pass...') + print('\nFirst version of the microservice generated. Start iterating on it to make the tests pass...') def generate_playground(self, microservice_name, microservice_path): - print_colored('', '############# Playground #############', 'blue') + print_colored('', '\n############# Playground #############', 'blue') file_name_to_content = get_all_microservice_files_with_content(microservice_path) conversation = self.gpt_session.get_conversation([]) @@ -169,7 +170,7 @@ metas: def debug_microservice(self, path, microservice_name, num_approach, packages): for i in range(1, MAX_DEBUGGING_ITERATIONS): print('Debugging iteration', i) - print('Trying to build the microservice. Might take a while...') + print('Trying to debug the microservice. Might take a while...') previous_microservice_path = get_microservice_path(path, microservice_name, packages, num_approach, i) next_microservice_path = get_microservice_path(path, microservice_name, packages, num_approach, i + 1) log_hubble = push_executor(previous_microservice_path) diff --git a/src/options/generate/prompt_system.py b/src/options/generate/prompt_system.py index 42ced44..6d443c1 100644 --- a/src/options/generate/prompt_system.py +++ b/src/options/generate/prompt_system.py @@ -72,7 +72,7 @@ print(response[0].text) ```''' -system_base_definition = '''It is the year 2021. +system_message_base = '''It is the year 2021. You are a principal engineer working at Jina - an open source company. You accurately satisfy all of the user's requirements. To be more specific, you help the user to build a microservice with the following requirements: diff --git a/src/options/generate/templates.py b/src/options/generate/templates.py index 6447e6d..606093e 100644 --- a/src/options/generate/templates.py +++ b/src/options/generate/templates.py @@ -95,7 +95,7 @@ For the implementation use the following package: '{packages}'. Obey the following rules: Have in mind that d.uri is never a path to a local file. It is always a url. -''' + not_allowed_executor_string() + ''' +''' + not_allowed_executor_string + ''' Your approach: 1. Identify the core challenge when implementing the executor. @@ -111,7 +111,7 @@ template_generate_test = PromptTemplate.from_template( {code_files_wrapped} -Write a single test case that tests the following scenario: '{test}'. In case the test scenario is not precise enough, test a general case without any assumptions. +Write a single test case that tests the following scenario: '{test_description}'. In case the test scenario is not precise enough, test a general case without any assumptions. Start the test with an extensive comment about the test case. Use the following import to import the executor: @@ -119,7 +119,7 @@ Use the following import to import the executor: from microservice import {microservice_name} ``` -''' + not_allowed_executor_string() + ''' +''' + not_allowed_executor_string + ''' The test must not open local files. The test must not mock a function of the executor. The test must not use other data than the one provided in the test scenario. @@ -153,7 +153,7 @@ The base image of the Dockerfile is FROM jinaai/jina:3.14.1-py39-standard. The entrypoint is ENTRYPOINT ["jina", "executor", "--uses", "config.yml"]. Make sure the all files are in the /workdir. The Dockerfile runs the test during the build process. -''' + not_allowed_docker_string() + '\n' + template_code_wrapping_string +''' + not_allowed_docker_string + '\n' + template_code_wrapping_string ) @@ -190,7 +190,7 @@ To solve this error, you should: 3. Write down the files that need to be changed, but not files that don't need to be changed. For files that need to be changed, you must provide the complete file with the exact same syntax to wrap the code. Obey the following rules: -''' + not_allowed_docker_string() + ''' +''' + not_allowed_docker_string + ''' You are given the following files: @@ -217,7 +217,7 @@ jina==2.0.0 template_solve_code_issue = PromptTemplate.from_template( '''General rules: -''' + not_allowed_executor_string() + ''' +''' + not_allowed_executor_string + ''' Here is the description of the task the executor must solve: {description} From a4d3a9f8698491c31c6c327c3404acbd50506715 Mon Sep 17 00:00:00 2001 From: Joschka Braun Date: Tue, 18 Apr 2023 14:55:56 +0200 Subject: [PATCH 8/9] fix: fix typos --- src/options/generate/generator.py | 8 ++++---- src/options/generate/templates.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/options/generate/generator.py b/src/options/generate/generator.py index 6414a94..d43eb49 100644 --- a/src/options/generate/generator.py +++ b/src/options/generate/generator.py @@ -36,8 +36,7 @@ class Generator: return '' def write_config_yml(self, microservice_name, dest_folder): - config_content = f''' -jtype: {microservice_name} + config_content = f'''jtype: {microservice_name} py_modules: - microservice.py metas: @@ -198,11 +197,12 @@ metas: key in ['requirements.txt', 'Dockerfile'] }) user_query = template_solve_dependency_issue.format( - description=self.task_description, summarized_error=summarized_error, all_files_string=all_files_string, + summarized_error=summarized_error, all_files_string=all_files_string, ) else: user_query = template_solve_code_issue.format( - description=self.task_description, summarized_error=summarized_error, all_files_string=self.files_to_string(file_name_to_content), + task_description=self.task_description, test_description=self.test_description, + summarized_error=summarized_error, all_files_string=self.files_to_string(file_name_to_content), ) conversation = self.gpt_session.get_conversation() returned_files_raw = conversation.chat(user_query) diff --git a/src/options/generate/templates.py b/src/options/generate/templates.py index 606093e..e9e92bd 100644 --- a/src/options/generate/templates.py +++ b/src/options/generate/templates.py @@ -220,10 +220,10 @@ template_solve_code_issue = PromptTemplate.from_template( ''' + not_allowed_executor_string + ''' Here is the description of the task the executor must solve: -{description} +{task_description} Here is the test scenario the executor must pass: -{test} +{test_description} Here are all the files I use: {all_files_string} From f6ed24e738d9bc59db5e841c7eb504cbf62ebf5d Mon Sep 17 00:00:00 2001 From: Joschka Braun Date: Tue, 18 Apr 2023 16:04:58 +0200 Subject: [PATCH 9/9] fix: review --- src/apis/gpt.py | 6 ++++-- src/options/generate/generator.py | 2 +- .../{prompt_system.py => templates_system.py} | 12 +++++++----- .../generate/{templates.py => templates_user.py} | 0 4 files changed, 12 insertions(+), 8 deletions(-) rename src/options/generate/{prompt_system.py => templates_system.py} (89%) rename src/options/generate/{templates.py => templates_user.py} (100%) diff --git a/src/apis/gpt.py b/src/apis/gpt.py index 59d1156..ddf6a25 100644 --- a/src/apis/gpt.py +++ b/src/apis/gpt.py @@ -11,7 +11,7 @@ from openai.error import RateLimitError from langchain.schema import AIMessage, HumanMessage, SystemMessage, BaseMessage from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler -from src.options.generate.prompt_system import system_message_base, executor_example, docarray_example, client_example +from src.options.generate.templates_system import template_system_message_base, executor_example, docarray_example, client_example from src.utils.string_tools import print_colored @@ -84,12 +84,14 @@ class _GPTConversation: print_colored('user', prompt, 'blue') print_colored('assistant', '', 'green', end='') response = self._chat([self.system_message] + self.messages) + if os.environ['VERBOSE'].lower() == 'true': + print() self.messages.append(response) return response.content @staticmethod def _create_system_message(task_description, test_description, system_definition_examples: List[str] = []) -> SystemMessage: - system_message = PromptTemplate.from_template(system_message_base).format( + system_message = PromptTemplate.from_template(template_system_message_base).format( task_description=task_description, test_description=test_description, ) diff --git a/src/options/generate/generator.py b/src/options/generate/generator.py index d43eb49..e3a93e0 100644 --- a/src/options/generate/generator.py +++ b/src/options/generate/generator.py @@ -7,7 +7,7 @@ from src.apis.jina_cloud import process_error_message, push_executor from src.constants import FILE_AND_TAG_PAIRS, NUM_IMPLEMENTATION_STRATEGIES, MAX_DEBUGGING_ITERATIONS, \ PROBLEMATIC_PACKAGES, EXECUTOR_FILE_NAME, EXECUTOR_FILE_TAG, TEST_EXECUTOR_FILE_NAME, TEST_EXECUTOR_FILE_TAG, \ REQUIREMENTS_FILE_NAME, REQUIREMENTS_FILE_TAG, DOCKER_FILE_NAME, DOCKER_FILE_TAG -from src.options.generate.templates import template_generate_microservice_name, template_generate_possible_packages, \ +from src.options.generate.templates_user import template_generate_microservice_name, template_generate_possible_packages, \ template_solve_code_issue, \ template_solve_dependency_issue, template_is_dependency_issue, template_generate_playground, \ template_generate_executor, template_generate_test, template_generate_requirements, template_generate_dockerfile, \ diff --git a/src/options/generate/prompt_system.py b/src/options/generate/templates_system.py similarity index 89% rename from src/options/generate/prompt_system.py rename to src/options/generate/templates_system.py index 6d443c1..b4fd136 100644 --- a/src/options/generate/prompt_system.py +++ b/src/options/generate/templates_system.py @@ -1,5 +1,5 @@ from src.constants import FLOW_URL_PLACEHOLDER -from src.options.generate.templates import not_allowed_docker_string, not_allowed_executor_string +from src.options.generate.templates_user import not_allowed_docker_string, not_allowed_executor_string executor_example = '''Using the Jina framework, users can define executors. Here is an example of how an executor can be defined. It always starts with a comment: @@ -72,16 +72,18 @@ print(response[0].text) ```''' -system_message_base = '''It is the year 2021. +template_system_message_base = f'''It is the year 2021. You are a principal engineer working at Jina - an open source company. You accurately satisfy all of the user's requirements. To be more specific, you help the user to build a microservice with the following requirements: ``` -{task_description} +{{task_description}} ``` and the following test scenario: ``` -{test_description} +{{test_description}} ``` -You must obey the following rules:''' + f'\n{not_allowed_executor_string}\n{not_allowed_docker_string}' +You must obey the following rules: +{not_allowed_executor_string} +{not_allowed_docker_string}''' diff --git a/src/options/generate/templates.py b/src/options/generate/templates_user.py similarity index 100% rename from src/options/generate/templates.py rename to src/options/generate/templates_user.py