diff --git a/micro_chain.py b/micro_chain.py index a47f69c..14eb995 100644 --- a/micro_chain.py +++ b/micro_chain.py @@ -6,7 +6,7 @@ from src import gpt, jina_cloud from src.constants import FILE_AND_TAG_PAIRS from src.jina_cloud import build_docker from src.prompt_tasks import general_guidelines, executor_file_task, chain_of_thought_creation, test_executor_file_task, \ - chain_of_thought_optimization, requirements_file_task, docker_file_task + chain_of_thought_optimization, requirements_file_task, docker_file_task, not_allowed from src.utils.io import recreate_folder, persist_file from src.utils.string_tools import print_colored @@ -15,17 +15,19 @@ def wrap_content_in_code_block(executor_content, file_name, tag): return f'**{file_name}**\n```{tag}\n{executor_content}\n```\n\n' -def main( + + +def create_executor( executor_description, input_modality, output_modality, test_scenario, - do_validation=True + executor_name ): input_doc_field = 'text' if input_modality == 'text' else 'blob' output_doc_field = 'text' if output_modality == 'text' else 'blob' # random integer at the end of the executor name to avoid name clashes - executor_name = f'MicroChainExecutor{random.randint(0, 1000_000)}' + recreate_folder('executor') EXECUTOR_FOLDER_v1 = 'executor/v1' recreate_folder(EXECUTOR_FOLDER_v1) @@ -40,9 +42,9 @@ def main( ) conversation = gpt.Conversation() conversation.query(user_query) - executor_content_raw = conversation.query(chain_of_thought_optimization('python', 'executor.py')) + executor_content_raw = conversation.query(f"General rules: " + not_allowed() + chain_of_thought_optimization('python', 'executor.py')) executor_content = extract_content_from_result(executor_content_raw, 'executor.py') - persist_file(executor_content, 'executor.py') + persist_file(executor_content, EXECUTOR_FOLDER_v1 + '/executor.py') print_colored('', '############# Test Executor #############', 'red') user_query = ( @@ -53,11 +55,12 @@ def main( conversation = gpt.Conversation() conversation.query(user_query) test_executor_content_raw = conversation.query( + f"General rules: " + not_allowed() + chain_of_thought_optimization('python', 'test_executor.py') + "Don't add any additional tests. " ) test_executor_content = extract_content_from_result(test_executor_content_raw, 'test_executor.py') - persist_file(test_executor_content, 'test_executor.py') + persist_file(test_executor_content, EXECUTOR_FOLDER_v1 + '/test_executor.py') print_colored('', '############# Requirements #############', 'red') user_query = ( @@ -71,7 +74,7 @@ def main( requirements_content_raw = conversation.query(chain_of_thought_optimization('', 'requirements.txt') + "Keep the same version of jina ") requirements_content = extract_content_from_result(requirements_content_raw, 'requirements.txt') - persist_file(requirements_content, 'requirements.txt') + persist_file(requirements_content, EXECUTOR_FOLDER_v1 + '/requirements.txt') print_colored('', '############# Dockerfile #############', 'red') user_query = ( @@ -83,24 +86,50 @@ def main( ) conversation = gpt.Conversation() conversation.query(user_query) - dockerfile_content_raw = conversation.query(chain_of_thought_optimization('dockerfile', 'Dockerfile')) + dockerfile_content_raw = conversation.query(f"General rules: " + not_allowed() + chain_of_thought_optimization('dockerfile', 'Dockerfile')) dockerfile_content = extract_content_from_result(dockerfile_content_raw, 'Dockerfile') - persist_file(dockerfile_content, 'Dockerfile') + persist_file(dockerfile_content, EXECUTOR_FOLDER_v1 + '/Dockerfile') write_config_yml(executor_name, EXECUTOR_FOLDER_v1) +def create_playground(executor_name, executor_path, host): + print_colored('', '############# Playground #############', 'red') + + file_name_to_content = get_all_executor_files_with_content(executor_path) + user_query = ( + general_guidelines() + + wrap_content_in_code_block(file_name_to_content['executor.py'], 'executor.py', 'python') + + wrap_content_in_code_block(file_name_to_content['test_executor.py'], 'test_executor.py', 'python') + + f''' +Create a playground for the executor {executor_name} using streamlit. +The executor is hosted on {host}. +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('/process', inputs=DocumentArray([d])) +print(response[0].text) # can also be blob in case of image/audio..., this should be visualized in the streamlit app +''' + ) + conversation = gpt.Conversation() + conversation.query(user_query) + playground_content_raw = conversation.query(f"General rules: " + not_allowed() + chain_of_thought_optimization('python', 'playground.py')) + playground_content = extract_content_from_result(playground_content_raw, 'playground.py') + persist_file(playground_content, f'{executor_path}/playground.py') + +def debug_executor(): for i in range(1, 20): - conversation = gpt.Conversation() + error = build_docker(f'executor/v{i}') if error: recreate_folder(f'executor/v{i + 1}') file_name_to_content = get_all_executor_files_with_content(f'executor/v{i}') all_files_string = files_to_string(file_name_to_content) user_query = ( - 'Here are all the files I use:\n' + f"General rules: " + not_allowed() + + 'Here are all the files I use:\n' + all_files_string + 'I got the following error:\n' - + error + + error + '\n' + 'Think quickly about possible reasons. ' 'Then output the files that need change. ' "Don't output files that don't need change. " @@ -111,6 +140,7 @@ def main( f"...code...\n" f"```\n\n" ) + conversation = gpt.Conversation() returned_files_raw = conversation.query(user_query) for file_name, tag in FILE_AND_TAG_PAIRS: updated_file = extract_content_from_result(returned_files_raw, file_name) @@ -121,33 +151,64 @@ def main( persist_file(content, f'executor/v{i + 1}/{file_name}') else: break + return f'executor/v{i}' - jina_cloud.push_executor('executor') - host = jina_cloud.deploy_flow(executor_name, do_validation, 'flow') - - # create playgorund and client.py - +def main( + executor_description, + input_modality, + output_modality, + test_scenario, +): + executor_name = f'MicroChainExecutor{random.randint(0, 1000_000)}' + create_executor(executor_description, input_modality, output_modality, test_scenario, executor_name) + executor_path = debug_executor() + print('Executor can be built locally, now we will push it to the cloud.') + jina_cloud.push_executor(executor_path) + print('Deploy a jina flow') + host = jina_cloud.deploy_flow(executor_name, 'flow') + print(f'Flow is deployed create the playground for {host}') + executor_name = 'MicroChainExecutor48442' + executor_path = 'executor/v2' + host = 'grpcs://mybelovedocrflow-24a412bc63.wolf.jina.ai' + create_playground(executor_name, executor_path, host) if __name__ == '__main__': # ######## Level 1 task ######### + main( + executor_description="The executor takes a pdf file as input, parses it and returns the text.", + input_modality='pdf', + output_modality='text', + test_scenario='Takes https://www2.deloitte.com/content/dam/Deloitte/de/Documents/about-deloitte/Deloitte-Unternehmensgeschichte.pdf and returns a string that is at least 100 characters long', + ) + # money prompt: $0.56 + # money generation: $0.22 + # total money: $0.78 + + + # ######## Level 2 task ######### # main( # executor_description="OCR detector", # input_modality='image', - # # input_doc_field='blob', # output_modality='text', - # # output_doc_field='text', # test_scenario='Takes https://miro.medium.com/v2/resize:fit:1024/0*4ty0Adbdg4dsVBo3.png as input and returns a string that contains "Hello, world"', - # do_validation=False # ) - ######### Level 2 task ######### - main( - executor_description="The executor takes 3D objects in obj format as input " - "and outputs a 2D image projection of that object where the full object is shown. ", - input_modality='3d', - output_modality='image', - test_scenario='Test that 3d object from https://raw.githubusercontent.com/polygonjs/polygonjs-assets/master/models/wolf.obj ' - 'is put in and out comes a 2d rendering of it', - do_validation=False - ) + + # ######## Level 3 task ######### + # main( + # executor_description="The executor takes an mp3 file as input and returns bpm and pitch in the tags.", + # input_modality='audio', + # output_modality='tags', + # test_scenario='Takes https://miro.medium.com/v2/resize:fit:1024/0*4ty0Adbdg4dsVBo3.png as input and returns a string that contains "Hello, world"', + # ) + + ######### Level 4 task ######### + # main( + # executor_description="The executor takes 3D objects in obj format as input " + # "and outputs a 2D image projection of that object where the full object is shown. ", + # input_modality='3d', + # output_modality='image', + # test_scenario='Test that 3d object from https://raw.githubusercontent.com/polygonjs/polygonjs-assets/master/models/wolf.obj ' + # 'is put in and out comes a 2d rendering of it', + # ) diff --git a/requirements.txt b/requirements.txt index f9f8362..746a9ec 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ -jina[perf]==3.14.2.dev18 -openai -ptest -jcloud -uvicorn \ No newline at end of file +jina==3.14.1 +pyrender~=0.1.45 +trimesh~=3.10.0 +numpy~=1.22.3 +Pillow~=9.0.1 +requests~=2.27.1 \ No newline at end of file diff --git a/src/gpt.py b/src/gpt.py index 38ba4c3..776b43e 100644 --- a/src/gpt.py +++ b/src/gpt.py @@ -11,6 +11,8 @@ from src.utils.string_tools import print_colored openai.api_key = os.environ['OPENAI_API_KEY'] +total_chars_prompt = 0 +total_chars_generation = 0 class Conversation: def __init__(self): @@ -26,6 +28,7 @@ class Conversation: def get_response(prompt_list: List[Tuple[str, str]]): + global total_chars_prompt, total_chars_generation for i in range(10): try: response_generator = openai.ChatCompletion.create( @@ -42,7 +45,7 @@ def get_response(prompt_list: List[Tuple[str, str]]): ] ) response_generator_with_timeout = timeout_generator_wrapper(response_generator, 5) - + total_chars_prompt += sum(len(prompt[1]) for prompt in prompt_list) complete_string = '' for chunk in response_generator_with_timeout: delta = chunk['choices'][0]['delta'] @@ -50,6 +53,13 @@ def get_response(prompt_list: List[Tuple[str, str]]): content = delta['content'] print_colored('' if complete_string else 'assistent', content, 'green', end='') complete_string += content + total_chars_generation += len(content) + print('\n') + money_prompt = round(total_chars_prompt / 3.4 * 0.03 / 1000, 2) + money_generation = round(total_chars_generation / 3.4 * 0.06 / 1000, 2) + print('money prompt:', f'${money_prompt}') + print('money generation:', f'${money_generation}') + print('total money:', f'${money_prompt + money_generation}') print('\n') return complete_string except (RateLimitError, Timeout, ConnectionError, GenerationTimeoutError) as e: diff --git a/src/jina_cloud.py b/src/jina_cloud.py index 12d5995..1ea1fa5 100644 --- a/src/jina_cloud.py +++ b/src/jina_cloud.py @@ -1,5 +1,4 @@ import os -from multiprocessing.connection import Client import subprocess import re @@ -7,7 +6,6 @@ import hubble from jcloud.flow import CloudFlow from jina import Flow -from src.constants import FLOW_URL_PLACEHOLDER def push_executor(dir_path): @@ -26,7 +24,7 @@ def deploy_on_jcloud(flow_yaml): -def deploy_flow(executor_name, do_validation, dest_folder): +def deploy_flow(executor_name, dest_folder): flow = f''' jtype: Flow with: @@ -53,11 +51,10 @@ executors: with open(full_flow_path, 'w') as f: f.write(flow) - if do_validation: - print('try local execution') - flow = Flow.load_config(full_flow_path) - with flow: - pass + print('try local execution') + flow = Flow.load_config(full_flow_path) + with flow: + pass print('deploy flow on jcloud') return deploy_on_jcloud(flow_yaml=full_flow_path) @@ -86,7 +83,7 @@ def build_docker(path): lines = error_message.split('\n') relevant_lines = [] - pattern = re.compile(r"^#\d+ \[\d+/\d+\]") # Pattern to match lines like "#11 [7/8]" + pattern = re.compile(r"^#\d+ \[[ \d]+/[ \d]+\]") # Pattern to match lines like "#11 [7/8]" last_matching_line_index = None for index, line in enumerate(lines): diff --git a/src/prompt_system.py b/src/prompt_system.py index 3f7bc2a..24ba6de 100644 --- a/src/prompt_system.py +++ b/src/prompt_system.py @@ -66,7 +66,13 @@ Here is an example of how a DocumentArray can be defined: from jina import DocumentArray, Document d1 = Document(text='hello') -d2 = Document(blob=b'\\x89PNG\\r\\n\\x1a\\n\\x00\\x00\\x00\\rIHDR\\x00\\x00\\x03L\\x00\\x00\\x01\\x18\\x08\\x06\\x00\\x00\\x00o...') + +# you can load binary data into a document +url = 'https://...' +response = requests.get(url) +obj_data = response.content +d2 = Document(blob=obj_data) # blob is bytes like b'\\x89PNG\\r\\n\\x1a\\n\ + d3 = Document(tensor=numpy.array([1, 2, 3]), chunks=[Document(uri=/local/path/to/file)] d4 = Document( uri='https://docs.docarray.org/img/logo.png', @@ -76,13 +82,13 @@ d5 = Document() d5.tensor = np.ones((2,4)) d5.uri = 'https://audio.com/audio.mp3' d6 = Document() -d6.blob = b'RIFF\\x00\\x00\\x00\\x00WAVEfmt \\x10\\x00...' +d6.blob # like b'RIFF\\x00\\x00\\x00\\x00WAVEfmt \\x10\\x00...' docs = DocumentArray([ d1, d2, d3, d4 ]) # the document has a helper function load_uri_to_blob: # For instance, d4.load_uri_to_blob() downloads the file from d4.uri and stores it in d4.blob. -# If d4.uri was something like 'https://website.web/img.jpg', then d4.blob would be something like b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01... +# If d4.uri was something like 'https://website.web/img.jpg', then d4.blob would be something like b'\\xff\\xd8\\xff\\xe0\\x00\\x10JFIF\\x00\\x01\\x01... ''' @@ -94,10 +100,10 @@ Here is an example of a client file: ```python from jina import Client, Document, DocumentArray client = Client(host='{FLOW_URL_PLACEHOLDER}') -d = Document(uri='data/img.png') +d = Document(uri='...') d.load_uri_to_blob() response = client.post('/process', inputs=DocumentArray([d])) -response[0].summary() +print(response[0].text) ``` ''' diff --git a/src/prompt_tasks.py b/src/prompt_tasks.py index 45ecbcc..e4d093b 100644 --- a/src/prompt_tasks.py +++ b/src/prompt_tasks.py @@ -35,16 +35,28 @@ It matches the following description: '{executor_description}'. It gets a DocumentArray as input where each document has the input modality '{input_modality}' and can be accessed via document.{input_doc_field}. It returns a DocumentArray as output where each document has the output modality '{output_modality}' that is stored in document.{output_doc_field}. Have in mind that d.uri is never a path to a local file. It is always a url. -The executor is not allowed to use the GPU. -The executor is not allowed to access a database. -The executor is not allowed to access a display. -The executor is not allowed to access external apis. -''', +''' + not_allowed(), 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}'. " + if test_scenario else "" + ) + + "Use the following import to import the executor: " + f"from executor import {executor_name} " + + not_allowed() + + "The test is not allowed to open local files. ", + TEST_EXECUTOR_FILE_TAG, + TEST_EXECUTOR_FILE_NAME + ) + def requirements_file_task(): return _task( "Write the content of the requirements.txt file. " @@ -56,21 +68,6 @@ def requirements_file_task(): ) -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}'. " - if test_scenario else "" - ) - + "Use the following import to import the executor: " - f"from executor import {executor_name} ", - TEST_EXECUTOR_FILE_TAG, - TEST_EXECUTOR_FILE_NAME - ) - - def docker_file_task(): return _task( "Write the Dockerfile that defines the environment with all necessary dependencies that the executor uses. " @@ -80,8 +77,9 @@ def docker_file_task(): "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. " "The base image of the Dockerfile is FROM jinaai/jina:3.14.1-py39-standard. " - 'The entrypoint is ENTRYPOINT ["jina", "executor", "--uses", "config.yml"] ' - "The Dockerfile runs the test during the build process. ", + '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_FILE_TAG, DOCKER_FILE_NAME ) @@ -106,8 +104,9 @@ def streamlit_file_task(): def chain_of_thought_creation(): return ( "First, write down some non-obvious thoughts about the challenges of the task and give multiple approaches on how you handle them. " - "For example, there are different libraries you could use. " - "Discuss the pros and cons for all of these approaches and then decide for one of the approaches. " + "For example, there are different libraries you could use and not all of them obay the rules: " + + not_allowed() + + "Discuss the pros and cons for all of these approaches and then decide for one of the approaches. " "Then write as I told you. " ) @@ -124,3 +123,11 @@ def chain_of_thought_optimization(tag_name, file_name): tag_name, file_name ) + +def not_allowed(): + return ''' +The executor is not allowed to use the GPU. +The executor is not allowed to access a database. +The executor is not allowed to access a display. +The executor is not allowed to access external apis. +''' \ No newline at end of file