From a65b5ff9dfb1e5aba11100bfd97ae34f4304d70d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Ho=CC=88nicke?= Date: Fri, 14 Apr 2023 16:07:05 +0200 Subject: [PATCH] feat: new structure --- README.md | 111 ++++++----- setup.py | 2 +- src/apis/__init__.py | 0 src/{ => apis}/gpt.py | 6 +- src/{ => apis}/jina_cloud.py | 111 +++++++++-- src/cli.py | 78 ++++++-- src/constants.py | 12 +- src/options/__init__.py | 37 ++++ src/options/configure/__init__.py | 0 src/{ => options/configure}/key_handling.py | 0 src/options/deploy/__init__.py | 0 src/options/deploy/deployer.py | 10 + src/options/generate/__init__.py | 0 .../generate/generator.py} | 178 ++++++++---------- src/{ => options/generate}/prompt_system.py | 3 +- src/{ => options/generate}/prompt_tasks.py | 2 +- src/options/run/__init__.py | 1 + src/options/run/runner.py | 11 ++ src/server.py | 53 ------ src/utils/io.py | 39 +++- test/__init__.py | 0 test/test_generator.py | 29 +++ 22 files changed, 436 insertions(+), 247 deletions(-) create mode 100644 src/apis/__init__.py rename src/{ => apis}/gpt.py (97%) rename src/{ => apis}/jina_cloud.py (61%) create mode 100644 src/options/__init__.py create mode 100644 src/options/configure/__init__.py rename src/{ => options/configure}/key_handling.py (100%) create mode 100644 src/options/deploy/__init__.py create mode 100644 src/options/deploy/deployer.py create mode 100644 src/options/generate/__init__.py rename src/{executor_factory.py => options/generate/generator.py} (58%) rename src/{ => options/generate}/prompt_system.py (95%) rename src/{ => options/generate}/prompt_tasks.py (99%) create mode 100644 src/options/run/__init__.py create mode 100644 src/options/run/runner.py delete mode 100644 src/server.py create mode 100644 test/__init__.py create mode 100644 test/test_generator.py diff --git a/README.md b/README.md index 165af05..25aa245 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@

-GPT Deploy: One line to create them all 🧙🚀 +GPT Deploy: One line to generate them all 🧙🚀

-

Jina NOW logo

@@ -53,21 +52,35 @@ If you set the environment variable `OPENAI_API_KEY`, the configuration step can Your api key must have access to gpt-4 to use this tool. We are working on a way to use gpt-3.5-turbo as well. -### Create Microservice +### Generate Microservice ```bash -gptdeploy create --description "Given a PDF, return its text" --test "https://www.africau.edu/images/default/sample.pdf" +gptdeploy generate --description "" --test "" --path . ``` -To create your personal microservice two things are required: +To generate your personal microservice two things are required: - A `description` of the task you want to accomplish. - A `test` scenario that ensures the microservice works as expected. +- A `path` on the local drive where the microservice will be generated. The creation process should take between 5 and 15 minutes. During this time, GPT iteratively builds your microservice until it finds a strategy that make your test scenario pass. -Once the microservice is created and deployed, you can test it using the generated Streamlit playground. -The deployment is made on the Jina`s infrastructure. -When creating a Jina account, you get some free credits, which you can use to deploy your microservice ($0.025/hour). -Be aware that the costs you have to pay for openai vary between $0.50 and $3.00 per microservice. -If you run out of credits, you can purchase more. + + +### Run Microservice +```bash +gptdeploy run --path +``` + +### Deploy Microservice +It is required to have a [Jina account](https://cloud.jina.ai/) to deploy your microservice. + +```bash +gptdeploy deploy --microservice_path +``` +[//]: # (Once the microservice is generated and deployed, you can test it using the generated Streamlit playground.) +[//]: # (The deployment is made on the Jina`s infrastructure. ) +[//]: # (When creating a Jina account, you get some free credits, which you can use to deploy your microservice ($0.025/hour).) +[//]: # (Be aware that the costs you have to pay for openai vary between $0.50 and $3.00 per microservice.) +[//]: # (If you run out of credits, you can purchase more.) ### Delete Microservice To save credits you can delete your microservice via the following commands: @@ -84,51 +97,51 @@ jc delete ### Animal Detector ```bash -gptdeploy create --description "Given an image, return the image with bounding boxes of all animals (https://pjreddie.com/media/files/yolov3.weights, https://raw.githubusercontent.com/pjreddie/darknet/master/cfg/yolov3.cfg)" --test "https://images.unsplash.com/photo-1444212477490-ca407925329e contains animals" +gptdeploy generate --description "Given an image, return the image with bounding boxes of all animals (https://pjreddie.com/media/files/yolov3.weights, https://raw.githubusercontent.com/pjreddie/darknet/master/cfg/yolov3.cfg)" --test "https://images.unsplash.com/photo-1444212477490-ca407925329e contains animals" ``` Animal Detector ### Meme Generator ```bash -gptdeploy create --description "Generate a meme from an image and a caption" --test "Surprised Pikachu: https://media.wired.com/photos/5f87340d114b38fa1f8339f9/master/w_1600%2Cc_limit/Ideas_Surprised_Pikachu_HD.jpg, TOP:When you discovered GPTDeploy" +gptdeploy generate --description "Generate a meme from an image and a caption" --test "Surprised Pikachu: https://media.wired.com/photos/5f87340d114b38fa1f8339f9/master/w_1600%2Cc_limit/Ideas_Surprised_Pikachu_HD.jpg, TOP:When you discovered GPTDeploy" ``` Meme Generator ### Rhyme Generator ```bash -gptdeploy create --description "Given a word, return a list of rhyming words using the datamuse api" --test "hello" +gptdeploy generate --description "Given a word, return a list of rhyming words using the datamuse api" --test "hello" ``` Rhyme Generator ### Word Cloud Generator ```bash -gptdeploy create --description "Generate a word cloud from a given text" --test "Lorem ipsum dolor sit amet, consectetur adipiscing elit." +gptdeploy generate --description "Generate a word cloud from a given text" --test "Lorem ipsum dolor sit amet, consectetur adipiscing elit." ``` Word Cloud Generator ### 3d model info ```bash -gptdeploy create --description "Given a 3d object, return vertex count and face count" --test "https://raw.githubusercontent.com/polygonjs/polygonjs-assets/master/models/wolf.obj" +gptdeploy generate --description "Given a 3d object, return vertex count and face count" --test "https://raw.githubusercontent.com/polygonjs/polygonjs-assets/master/models/wolf.obj" ``` 3D Model Info ### Table extraction ```bash -gptdeploy create --description "Given a URL, extract all tables as csv" --test "http://www.ins.tn/statistiques/90" +gptdeploy generate --description "Given a URL, extract all tables as csv" --test "http://www.ins.tn/statistiques/90" ``` Table Extraction ### Audio to mel spectrogram ```bash -gptdeploy create --description "Create mel spectrograms from audio file" --test "https://cdn.pixabay.com/download/audio/2023/02/28/audio_550d815fa5.mp3" +gptdeploy generate --description "Create mel spectrograms from audio file" --test "https://cdn.pixabay.com/download/audio/2023/02/28/audio_550d815fa5.mp3" ``` Audio to Mel Spectrogram ### Text to speech ```bash -gptdeploy create --description "Convert text to speech" --test "Hello, welcome to GPT Deploy!" +gptdeploy generate --description "Convert text to speech" --test "Hello, welcome to GPT Deploy!" ``` Text to Speech @@ -139,20 +152,20 @@ gptdeploy create --description "Convert text to speech" --test "Hello, welcome t ### Heatmap Generator ```bash -gptdeploy create --description "Create a heatmap from an image and a list of relative coordinates" --test "https://images.unsplash.com/photo-1574786198875-49f5d09fe2d2, [[0.1, 0.2], [0.3, 0.4], [0.5, 0.6], [0.2, 0.1], [0.7, 0.2], [0.4, 0.2]]" +gptdeploy generate --description "Create a heatmap from an image and a list of relative coordinates" --test "https://images.unsplash.com/photo-1574786198875-49f5d09fe2d2, [[0.1, 0.2], [0.3, 0.4], [0.5, 0.6], [0.2, 0.1], [0.7, 0.2], [0.4, 0.2]]" ``` Heatmap Generator ### QR Code Generator ```bash -gptdeploy create --description "Generate QR code from URL" --test "https://www.example.com" +gptdeploy generate --description "Generate QR code from URL" --test "https://www.example.com" ``` QR Code Generator ### Mandelbrot Set Visualizer ```bash -gptdeploy create --description "Visualize the Mandelbrot set with custom parameters" --test "center=-0+1i, zoom=1.0, size=800x800, iterations=1000" +gptdeploy generate --description "Visualize the Mandelbrot set with custom parameters" --test "center=-0+1i, zoom=1.0, size=800x800, iterations=1000" ``` Mandelbrot Set Visualizer @@ -164,7 +177,7 @@ gptdeploy create --description "Visualize the Mandelbrot set with custom paramet [//]: # (```bash) -[//]: # (gptdeploy create --description "Given a password, return a score from 1 to 10 indicating the strength of the password" --test "Pa$$w0rd => 1/5, !Akfdh%.ytRadf => 5/5") +[//]: # (gptdeploy generate --description "Given a password, return a score from 1 to 10 indicating the strength of the password" --test "Pa$$w0rd => 1/5, !Akfdh%.ytRadf => 5/5") [//]: # (```) @@ -172,7 +185,7 @@ gptdeploy create --description "Visualize the Mandelbrot set with custom paramet [//]: # (```bash) -[//]: # (gptdeploy create --description "Convert text to morse code" --test "Hello, welcome to GPT Deploy!") +[//]: # (gptdeploy generate --description "Convert text to morse code" --test "Hello, welcome to GPT Deploy!") [//]: # (```) @@ -180,7 +193,7 @@ gptdeploy create --description "Visualize the Mandelbrot set with custom paramet [//]: # (```bash) -[//]: # (gptdeploy create --description "Given an IP address, return the geolocation information" --test "142.251.46.174") +[//]: # (gptdeploy generate --description "Given an IP address, return the geolocation information" --test "142.251.46.174") [//]: # (```) @@ -188,7 +201,7 @@ gptdeploy create --description "Visualize the Mandelbrot set with custom paramet [//]: # (```bash) -[//]: # (gptdeploy create --description "Converts any currency into any other" --test "1 usd to eur") +[//]: # (gptdeploy generate --description "Converts any currency into any other" --test "1 usd to eur") [//]: # (```) @@ -196,7 +209,7 @@ gptdeploy create --description "Visualize the Mandelbrot set with custom paramet [//]: # (```bash) -[//]: # (gptdeploy create --description "Given an image, resize it to a specified width and height" --test "https://images.unsplash.com/photo-1602738328654-51ab2ae6c4ff") +[//]: # (gptdeploy generate --description "Given an image, resize it to a specified width and height" --test "https://images.unsplash.com/photo-1602738328654-51ab2ae6c4ff") [//]: # (```) @@ -204,7 +217,7 @@ gptdeploy create --description "Visualize the Mandelbrot set with custom paramet [//]: # (```bash) -[//]: # (gptdeploy create --description "Given a city, return the current weather" --test "Berlin") +[//]: # (gptdeploy generate --description "Given a city, return the current weather" --test "Berlin") [//]: # (```) @@ -213,7 +226,7 @@ gptdeploy create --description "Visualize the Mandelbrot set with custom paramet [//]: # (```bash) -[//]: # (gptdeploy create --description "Given a sudoku puzzle, return the solution" --test "[[2, 5, 0, 0, 3, 0, 9, 0, 1], [0, 1, 0, 0, 0, 4, 0, 0, 0], [4, 0, 7, 0, 0, 0, 2, 0, 8], [0, 0, 5, 2, 0, 0, 0, 0, 0], [0, 0, 0, 0, 9, 8, 1, 0, 0], [0, 4, 0, 0, 0, 3, 0, 0, 0], [0, 0, 0, 3, 6, 0, 0, 7, 2], [0, 7, 0, 0, 0, 0, 0, 0, 3], [9, 0, 3, 0, 0, 0, 6, 0, 4]]") +[//]: # (gptdeploy generate --description "Given a sudoku puzzle, return the solution" --test "[[2, 5, 0, 0, 3, 0, 9, 0, 1], [0, 1, 0, 0, 0, 4, 0, 0, 0], [4, 0, 7, 0, 0, 0, 2, 0, 8], [0, 0, 5, 2, 0, 0, 0, 0, 0], [0, 0, 0, 0, 9, 8, 1, 0, 0], [0, 4, 0, 0, 0, 3, 0, 0, 0], [0, 0, 0, 3, 6, 0, 0, 7, 2], [0, 7, 0, 0, 0, 0, 0, 0, 3], [9, 0, 3, 0, 0, 0, 6, 0, 4]]") [//]: # (```) @@ -222,7 +235,7 @@ gptdeploy create --description "Visualize the Mandelbrot set with custom paramet [//]: # (```bash) -[//]: # (gptdeploy create --description "Estimate a company's carbon footprint based on factors like transportation, electricity usage, waste production etc..." --test "Jina AI") +[//]: # (gptdeploy generate --description "Estimate a company's carbon footprint based on factors like transportation, electricity usage, waste production etc..." --test "Jina AI") [//]: # (```) @@ -231,7 +244,7 @@ gptdeploy create --description "Visualize the Mandelbrot set with custom paramet [//]: # (```bash) -[//]: # (gptdeploy create --description "Create a microservice that estimates the value of a property based on factors like location, property type, age, and square footage." --test "Berlin Friedrichshain, Flat, 100m², 10 years old") +[//]: # (gptdeploy generate --description "Create a microservice that estimates the value of a property based on factors like location, property type, age, and square footage." --test "Berlin Friedrichshain, Flat, 100m², 10 years old") [//]: # (```) @@ -240,7 +253,7 @@ gptdeploy create --description "Visualize the Mandelbrot set with custom paramet [//]: # (```bash) -[//]: # (gptdeploy create --description "Align two DNA or RNA sequences using the Needleman-Wunsch algorithm" --test "AGTC, GTCA") +[//]: # (gptdeploy generate --description "Align two DNA or RNA sequences using the Needleman-Wunsch algorithm" --test "AGTC, GTCA") [//]: # (```) @@ -249,7 +262,7 @@ gptdeploy create --description "Visualize the Mandelbrot set with custom paramet [//]: # (```bash) -[//]: # (gptdeploy create --description "Convert markdown to HTML" --test "# Hello, welcome to GPT Deploy!") +[//]: # (gptdeploy generate --description "Convert markdown to HTML" --test "# Hello, welcome to GPT Deploy!") [//]: # (```) @@ -258,7 +271,7 @@ gptdeploy create --description "Visualize the Mandelbrot set with custom paramet [//]: # (```bash) -[//]: # (gptdeploy create --description "Generate a barcode from a string" --test "Hello, welcome to GPT Deploy!") +[//]: # (gptdeploy generate --description "Generate a barcode from a string" --test "Hello, welcome to GPT Deploy!") [//]: # (```) @@ -267,7 +280,7 @@ gptdeploy create --description "Visualize the Mandelbrot set with custom paramet [//]: # (```bash) -[//]: # (gptdeploy create --description "Compress a file using the gzip algorithm" --test "content of the file: Hello, welcome to GPT Deploy!") +[//]: # (gptdeploy generate --description "Compress a file using the gzip algorithm" --test "content of the file: Hello, welcome to GPT Deploy!") [//]: # (```) @@ -276,7 +289,7 @@ gptdeploy create --description "Visualize the Mandelbrot set with custom paramet [//]: # (```bash) -[//]: # (gptdeploy create --description "Add a watermark (GPT Deploy) to an image" --test "https://images.unsplash.com/photo-1602738328654-51ab2ae6c4ff") +[//]: # (gptdeploy generate --description "Add a watermark (GPT Deploy) to an image" --test "https://images.unsplash.com/photo-1602738328654-51ab2ae6c4ff") [//]: # (```) @@ -285,7 +298,7 @@ gptdeploy create --description "Visualize the Mandelbrot set with custom paramet [//]: # (```bash) -[//]: # (gptdeploy create --description "Extract metadata from a file" --test "https://images.unsplash.com/photo-1602738328654-51ab2ae6c4ff") +[//]: # (gptdeploy generate --description "Extract metadata from a file" --test "https://images.unsplash.com/photo-1602738328654-51ab2ae6c4ff") [//]: # (```) @@ -294,7 +307,7 @@ gptdeploy create --description "Visualize the Mandelbrot set with custom paramet [//]: # (```bash) -[//]: # (gptdeploy create --description "Extract a thumbnail from a video" --test "http://techslides.com/demos/sample-videos/small.mp4") +[//]: # (gptdeploy generate --description "Extract a thumbnail from a video" --test "http://techslides.com/demos/sample-videos/small.mp4") [//]: # (```) @@ -303,7 +316,7 @@ gptdeploy create --description "Visualize the Mandelbrot set with custom paramet [//]: # (```bash) -[//]: # (gptdeploy create --description "Create a gif from a list of images" --test "https://images.unsplash.com/photo-1564725075388-cc8338732289, https://images.unsplash.com/photo-1584555684040-bad07f46a21f, https://images.unsplash.com/photo-1584555613497-9ecf9dd06f68") +[//]: # (gptdeploy generate --description "Create a gif from a list of images" --test "https://images.unsplash.com/photo-1564725075388-cc8338732289, https://images.unsplash.com/photo-1584555684040-bad07f46a21f, https://images.unsplash.com/photo-1584555613497-9ecf9dd06f68") [//]: # (```) @@ -316,7 +329,7 @@ gptdeploy create --description "Visualize the Mandelbrot set with custom paramet [//]: # () [//]: # (```bash) -[//]: # (gptdeploy create --description "Visualize a sound file and output the visualization as video combined with the sound" --test "some/mp3/file.mp3") +[//]: # (gptdeploy generate --description "Visualize a sound file and output the visualization as video combined with the sound" --test "some/mp3/file.mp3") [//]: # (```) @@ -326,7 +339,7 @@ gptdeploy create --description "Visualize the Mandelbrot set with custom paramet [//]: # (```bash) -[//]: # (gptdeploy create --description "Convert a chemical formula into a 2D chemical structure diagram" --test "C6H6") +[//]: # (gptdeploy generate --description "Convert a chemical formula into a 2D chemical structure diagram" --test "C6H6") [//]: # (```) @@ -334,7 +347,7 @@ gptdeploy create --description "Visualize the Mandelbrot set with custom paramet [//]: # (```bash) -[//]: # (gptdeploy create --description "creates aesthetically pleasing color palettes based on a seed color, using color theory principles like complementary or analogous colors" --test "red") +[//]: # (gptdeploy generate --description "creates aesthetically pleasing color palettes based on a seed color, using color theory principles like complementary or analogous colors" --test "red") [//]: # (```) @@ -343,7 +356,7 @@ gptdeploy create --description "Visualize the Mandelbrot set with custom paramet [//]: # (```bash) -[//]: # (gptdeploy create --description "Generate a depth map from a 3d Object" --test "https://raw.githubusercontent.com/polygonjs/polygonjs-assets/master/models/wolf.obj") +[//]: # (gptdeploy generate --description "Generate a depth map from a 3d Object" --test "https://raw.githubusercontent.com/polygonjs/polygonjs-assets/master/models/wolf.obj") [//]: # (```) @@ -355,7 +368,7 @@ gptdeploy create --description "Visualize the Mandelbrot set with custom paramet [//]: # (```bash) -[//]: # (gptdeploy create --description "Convert image to ASCII art" --test "https://images.unsplash.com/photo-1602738328654-51ab2ae6c4ff") +[//]: # (gptdeploy generate --description "Convert image to ASCII art" --test "https://images.unsplash.com/photo-1602738328654-51ab2ae6c4ff") [//]: # (```) @@ -390,7 +403,7 @@ graph TB registry --> deploy[deploy microservice] - deploy --> streamlit[create streamlit playground] + deploy --> streamlit[generate streamlit playground] streamlit --> user_run[user tests microservice] @@ -398,21 +411,21 @@ graph TB 1. GPT Deploy identifies several strategies to implement your task. 2. It tests each strategy until it finds one that works. -3. For each strategy, it creates the following files: -- executor.py: This is the main implementation of the microservice. -- test_executor.py: These are test cases to ensure the microservice works as expected. +3. For each strategy, it generates the following files: +- microservice.py: This is the main implementation of the microservice. +- test_microservice.py: These are test cases to ensure the microservice works as expected. - requirements.txt: This file lists the packages needed by the microservice and its tests. - Dockerfile: This file is used to run the microservice in a container and also runs the tests when building the image. 4. GPT Deploy attempts to build the image. If the build fails, it uses the error message to apply a fix and tries again to build the image. 5. Once it finds a successful strategy, it: - Pushes the Docker image to the registry. - Deploys the microservice. -- Creates a Streamlit playground where you can test the microservice. +- Generates a Streamlit playground where you can test the microservice. 6. If it fails 10 times in a row, it moves on to the next approach. ## 🔮 vision -Use natural language interface to create, deploy and update your microservice infrastructure. +Use natural language interface to generate, deploy and update your microservice infrastructure. ## ✨ Contributers If you want to contribute to this project, feel free to open a PR or an issue. diff --git a/setup.py b/setup.py index a23a7de..63d2bb3 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ def read_requirements(): setup( name='gptdeploy', version='0.18.15', - description='Use natural language interface to create, deploy and update your microservice infrastructure.', + description='Use natural language interface to generate, deploy and update your microservice infrastructure.', long_description=open('README.md', 'r', encoding='utf-8').read(), long_description_content_type='text/markdown', author='Florian Hönicke', diff --git a/src/apis/__init__.py b/src/apis/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/gpt.py b/src/apis/gpt.py similarity index 97% rename from src/gpt.py rename to src/apis/gpt.py index 4dd10c0..63fbd03 100644 --- a/src/gpt.py +++ b/src/apis/gpt.py @@ -7,13 +7,13 @@ from openai.error import RateLimitError, Timeout from src.constants import PRICING_GPT4_PROMPT, PRICING_GPT4_GENERATION, PRICING_GPT3_5_TURBO_PROMPT, \ PRICING_GPT3_5_TURBO_GENERATION -from src.prompt_system import system_base_definition +from src.options.generate.prompt_system import system_base_definition from src.utils.io import timeout_generator_wrapper, GenerationTimeoutError from src.utils.string_tools import print_colored class GPTSession: def __init__(self): - self.get_openai_api_key() + self.configure_openai_api_key() if self.is_gpt4_available(): self.supported_model = 'gpt-4' self.pricing_prompt = PRICING_GPT4_PROMPT @@ -26,7 +26,7 @@ class GPTSession: self.chars_prompt_so_far = 0 self.chars_generation_so_far = 0 - def get_openai_api_key(self): + def configure_openai_api_key(self): if 'OPENAI_API_KEY' not in os.environ: raise Exception(''' You need to set OPENAI_API_KEY in your environment. diff --git a/src/jina_cloud.py b/src/apis/jina_cloud.py similarity index 61% rename from src/jina_cloud.py rename to src/apis/jina_cloud.py index 5f2466e..df44a3a 100644 --- a/src/jina_cloud.py +++ b/src/apis/jina_cloud.py @@ -3,17 +3,41 @@ import json import os import re import subprocess +import threading import webbrowser from pathlib import Path +import click import hubble from hubble.executor.helper import upload_file, archive_package, get_request_header from jcloud.flow import CloudFlow +from jina import Flow -from src.utils.io import suppress_stdout +from src.utils.io import suppress_stdout, is_docker_running from src.utils.string_tools import print_colored +import requests +import time + +def wait_until_app_is_ready(url): + is_app_ready = False + while not is_app_ready: + try: + response = requests.get(url) + print('waiting for app to be ready...') + if response.status_code == 200: + is_app_ready = True + except requests.exceptions.RequestException: + pass + time.sleep(0.5) + +def open_streamlit_app(): + url = "http://localhost:8081/playground" + wait_until_app_is_ready(url) + webbrowser.open(url, new=2) + + def redirect_callback(href): print( f'You need login to Jina first to use GPTDeploy\n' @@ -85,29 +109,89 @@ def get_user_name(): return response['data']['name'] -def deploy_on_jcloud(flow_yaml): +def _deploy_on_jcloud(flow_yaml): cloud_flow = CloudFlow(path=flow_yaml) return cloud_flow.__enter__().endpoints['gateway'] -def deploy_flow(executor_name, dest_folder): +def deploy_on_jcloud(executor_name, microservice_path): print('Deploy a jina flow') + full_flow_path = create_flow_yaml(microservice_path, executor_name, use_docker=True) + + for i in range(3): + try: + host = _deploy_on_jcloud(flow_yaml=full_flow_path) + break + except Exception as e: + print(f'Could not deploy on Jina Cloud. Trying again in 5 seconds. Error: {e}') + time.sleep(5) + if i == 2: + raise Exception(''' + Could not deploy on Jina Cloud. + This can happen when the microservice is buggy, if it requires too much memory or if the Jina Cloud is overloaded. + Please try again later. +''' + ) + + print(f''' +Your Microservice is deployed. +Run the following command to start the playground: + +streamlit run {os.path.join(microservice_path, "app.py")} --server.port 8081 --server.address 0.0.0.0 -- --host http://{host} +''' + ) + return host + +def run_streamlit_app(app_path): + subprocess.run(['streamlit', 'run', app_path, 'server.address', '0.0.0.0', '--server.port', '8081', '--', '--host', 'grpc://localhost:8080']) + + +def run_locally(executor_name, microservice_version_path): + if is_docker_running(): + use_docker = True + else: + click.echo('Docker daemon doesn\'t seem to be running. Trying to start it without docker') + use_docker = False + print('Run a jina flow locally') + full_flow_path = create_flow_yaml(microservice_version_path, executor_name, use_docker) + flow = Flow.load_config(full_flow_path) + with flow: + print(f''' +Your microservice started locally. +We now start the playground for you. +''') + + app_path = os.path.join(microservice_version_path, "app.py") + + # Run the Streamlit app in a separate thread + streamlit_thread = threading.Thread(target=run_streamlit_app, args=(app_path,)) + streamlit_thread.start() + + # Open the Streamlit app in the user's default web browser + open_streamlit_app() + + flow.block() + +def create_flow_yaml(dest_folder, executor_name, use_docker): + if use_docker: + prefix = 'jinaai+docker' + else: + prefix = 'jinaai' flow = f''' jtype: Flow with: name: nowapi - env: - JINA_LOG_LEVEL: DEBUG + port: 8080 jcloud: version: 3.14.2.dev18 labels: creator: microchain name: gptdeploy + executors: - name: {executor_name.lower()} - uses: jinaai+docker://{get_user_name()}/{executor_name}:latest - env: - JINA_LOG_LEVEL: DEBUG + uses: {prefix }://{get_user_name()}/{executor_name}:latest + {"" if use_docker else "install-requirements: True"} jcloud: resources: instance: C2 @@ -117,16 +201,7 @@ executors: 'flow.yml') with open(full_flow_path, 'w') as f: f.write(flow) - - for i in range(3): - try: - host = deploy_on_jcloud(flow_yaml=full_flow_path) - break - except Exception as e: - raise e - - print(f'Flow is deployed create the playground for {host}') - return host + return full_flow_path def replace_client_line(file_content: str, replacement: str) -> str: diff --git a/src/cli.py b/src/cli.py index 2c0bc37..2a637de 100644 --- a/src/cli.py +++ b/src/cli.py @@ -1,30 +1,80 @@ +import functools +import os + import click -from src.executor_factory import ExecutorFactory -from src.jina_cloud import jina_auth_login -from src.key_handling import set_api_key +from src.apis.jina_cloud import jina_auth_login +from src.options.configure.key_handling import set_api_key + + +def exception_interceptor(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + try: + return func(*args, **kwargs) + except Exception as e: + raise type(e)(f''' +{str(e)} + +😱😱😱 Sorry for this experience. Could you please report an issue about this on our github repo? We'll try to fix it asap. +''') from e + return wrapper + +def path_param(func): + @click.option('--path', required=True, help='Path to the generated microservice.') + @functools.wraps(func) + def wrapper(*args, **kwargs): + path = os.path.expanduser(kwargs['path']) + path = os.path.abspath(path) + kwargs['path'] = path + return func(*args, **kwargs) + return wrapper @click.group(invoke_without_command=True) -def main(): +@click.pass_context +@exception_interceptor +def main(ctx): + if ctx.invoked_subcommand is None: + click.echo(ctx.get_help()) jina_auth_login() @main.command() -@click.option('--description', required=True, help='Description of the executor.') -@click.option('--test', required=True, help='Test scenario for the executor.') -@click.option('--num_approaches', default=3, type=int, - help='Number of num_approaches to use to fulfill the task (default: 3).') -@click.option('--output_path', default='executor', help='Path to the output folder (must be empty). ') -def create( +@click.option('--description', required=True, help='Description of the microservice.') +@click.option('--test', required=True, help='Test scenario for the microservice.') +@path_param +def generate( description, test, - num_approaches=3, - output_path='executor', + path, ): - executor_factory = ExecutorFactory() - executor_factory.create(description, num_approaches, output_path, test) + from src.options.generate.generator import Generator + path = os.path.expanduser(path) + path = os.path.abspath(path) + if os.path.exists(path): + if os.listdir(path): + click.echo(f"Error: The path {path} you provided via --path is not empty. Please choose a directory that does not exist or is empty.") + return + generator = Generator() + generator.generate(description, test, path) +@main.command() +@path_param +def run(path): + from src.options.run import Runner + path = os.path.expanduser(path) + path = os.path.abspath(path) + Runner().run(path) + + +@main.command() +@path_param +def deploy(path): + from src.options.deploy.deployer import Deployer + path = os.path.expanduser(path) + path = os.path.abspath(path) + Deployer().deploy(path) @main.command() @click.option('--key', required=True, help='Your OpenAI API key.') diff --git a/src/constants.py b/src/constants.py index c44d4da..a12b8fe 100644 --- a/src/constants.py +++ b/src/constants.py @@ -1,5 +1,5 @@ -EXECUTOR_FILE_NAME = 'executor.py' -TEST_EXECUTOR_FILE_NAME = 'test_executor.py' +EXECUTOR_FILE_NAME = 'microservice.py' +TEST_EXECUTOR_FILE_NAME = 'test_microservice.py' REQUIREMENTS_FILE_NAME = 'requirements.txt' DOCKER_FILE_NAME = 'Dockerfile' CLIENT_FILE_NAME = 'client.py' @@ -21,12 +21,12 @@ FILE_AND_TAG_PAIRS = [ (STREAMLIT_FILE_NAME, STREAMLIT_FILE_TAG) ] -EXECUTOR_FOLDER_v1 = 'executor_v1' -EXECUTOR_FOLDER_v2 = 'executor_v2' - 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 \ No newline at end of file +PRICING_GPT3_5_TURBO_GENERATION = 0.002 + +NUM_IMPLEMENTATION_STRATEGIES = 3 +MAX_DEBUGGING_ITERATIONS = 10 \ No newline at end of file diff --git a/src/options/__init__.py b/src/options/__init__.py new file mode 100644 index 0000000..37a4130 --- /dev/null +++ b/src/options/__init__.py @@ -0,0 +1,37 @@ +import os + + +def get_latest_folder(path): + return max([os.path.join(path, f) for f in os.listdir(path) if os.path.isdir(os.path.join(path, f))]) + +def get_latest_version_path(microservice_path): + executor_name_path = get_latest_folder(microservice_path) + latest_approach_path = get_latest_folder(executor_name_path) + latest_version_path = get_latest_folder(latest_approach_path) + return latest_version_path + +def get_executor_name(microservice_path): + return get_latest_folder(microservice_path).split('/')[-1] + + +def validate_folder_is_correct(microservice_path): + if not os.path.exists(microservice_path): + raise ValueError(f'Path {microservice_path} does not exist') + if not os.path.isdir(microservice_path): + raise ValueError(f'Path {microservice_path} is not a directory') + if len(os.listdir(microservice_path)) == 0: + raise ValueError(f'Path {microservice_path} is empty. Please generate a microservice first. Type `gptdeploy generate` for further instructions.') + if len(os.listdir(microservice_path)) > 1: + raise ValueError(f'Path {microservice_path} needs to contain only one folder. Please make sure that you only have one microservice in this folder.') + latest_version_path = get_latest_version_path(microservice_path) + required_files = [ + 'app.py', + 'requirements.txt', + 'Dockerfile', + 'config.yml', + 'microservice.py', + 'test_microservice.py', + ] + for file_name in required_files: + if not os.path.exists(os.path.join(latest_version_path, file_name)): + raise ValueError(f'Path {latest_version_path} needs to contain a file named {file_name}') diff --git a/src/options/configure/__init__.py b/src/options/configure/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/key_handling.py b/src/options/configure/key_handling.py similarity index 100% rename from src/key_handling.py rename to src/options/configure/key_handling.py diff --git a/src/options/deploy/__init__.py b/src/options/deploy/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/options/deploy/deployer.py b/src/options/deploy/deployer.py new file mode 100644 index 0000000..ca09b30 --- /dev/null +++ b/src/options/deploy/deployer.py @@ -0,0 +1,10 @@ +from src.apis.jina_cloud import deploy_on_jcloud +from src.options import validate_folder_is_correct, get_executor_name, get_latest_version_path + + +class Deployer: + def deploy(self, microservice_path): + validate_folder_is_correct(microservice_path) + executor_name = get_executor_name(microservice_path) + latest_version_path = get_latest_version_path(microservice_path) + deploy_on_jcloud(executor_name, latest_version_path) \ No newline at end of file diff --git a/src/options/generate/__init__.py b/src/options/generate/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/executor_factory.py b/src/options/generate/generator.py similarity index 58% rename from src/executor_factory.py rename to src/options/generate/generator.py index c8923c6..d8c8fdb 100644 --- a/src/executor_factory.py +++ b/src/options/generate/generator.py @@ -2,16 +2,16 @@ import os import random import re -from src import gpt, jina_cloud -from src.constants import FILE_AND_TAG_PAIRS -from src.jina_cloud import push_executor, process_error_message -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, not_allowed -from src.utils.io import persist_file +from src.apis import gpt +from src.constants import FILE_AND_TAG_PAIRS, NUM_IMPLEMENTATION_STRATEGIES, MAX_DEBUGGING_ITERATIONS +from src.apis.jina_cloud import process_error_message, push_executor +from src.options.generate.prompt_tasks import general_guidelines, chain_of_thought_creation, executor_file_task, \ + not_allowed, chain_of_thought_optimization, test_executor_file_task, requirements_file_task, docker_file_task +from src.utils.io import persist_file, get_all_microservice_files_with_content, get_microservice_path from src.utils.string_tools import print_colored -class ExecutorFactory: +class Generator: def __init__(self): self.gpt_session = gpt.GPTSession() @@ -23,93 +23,81 @@ class ExecutorFactory: else: return '' - def write_config_yml(self, executor_name, dest_folder): + def write_config_yml(self, microservice_name, dest_folder): config_content = f''' - jtype: {executor_name} + jtype: {microservice_name} py_modules: - - executor.py + - microservice.py metas: - name: {executor_name} + name: {microservice_name} ''' with open(os.path.join(dest_folder, 'config.yml'), 'w') as f: f.write(config_content) - def get_all_executor_files_with_content(self, folder_path): - file_name_to_content = {} - for filename in os.listdir(folder_path): - file_path = os.path.join(folder_path, filename) - - if os.path.isfile(file_path): - with open(file_path, 'r', encoding='utf-8') as file: - content = file.read() - file_name_to_content[filename] = content - - return file_name_to_content - def files_to_string(self, file_name_to_content): - all_executor_files_string = '' + all_microservice_files_string = '' for file_name, tag in FILE_AND_TAG_PAIRS: if file_name in file_name_to_content: - all_executor_files_string += f'**{file_name}**\n' - all_executor_files_string += f'```{tag}\n' - all_executor_files_string += file_name_to_content[file_name] - all_executor_files_string += '\n```\n\n' - return all_executor_files_string + 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```\n\n' + return all_microservice_files_string - def wrap_content_in_code_block(self, executor_content, file_name, tag): - return f'**{file_name}**\n```{tag}\n{executor_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 create_executor( + def generate_microservice( self, description, test, - output_path, - executor_name, + path, + microservice_name, package, num_approach, is_chain_of_thought=False, ): - EXECUTOR_FOLDER_v1 = self.get_executor_path(output_path, executor_name, package, num_approach, 1) - os.makedirs(EXECUTOR_FOLDER_v1) + MICROSERVICE_FOLDER_v1 = get_microservice_path(path, microservice_name, package, num_approach, 1) + os.makedirs(MICROSERVICE_FOLDER_v1) - print_colored('', '############# Executor #############', 'red') + print_colored('', '############# Microservice #############', 'red') user_query = ( general_guidelines() - + executor_file_task(executor_name, description, test, package) + + executor_file_task(microservice_name, description, test, package) + chain_of_thought_creation() ) conversation = self.gpt_session.get_conversation() - executor_content_raw = conversation.query(user_query) + microservice_content_raw = conversation.query(user_query) if is_chain_of_thought: - executor_content_raw = conversation.query( - f"General rules: " + not_allowed() + chain_of_thought_optimization('python', 'executor.py')) - executor_content = self.extract_content_from_result(executor_content_raw, 'executor.py') + microservice_content_raw = conversation.query( + f"General rules: " + not_allowed() + chain_of_thought_optimization('python', 'microservice.py')) + microservice_content = self.extract_content_from_result(microservice_content_raw, 'microservice.py') - persist_file(executor_content, os.path.join(EXECUTOR_FOLDER_v1, 'executor.py')) + persist_file(microservice_content, os.path.join(MICROSERVICE_FOLDER_v1, 'microservice.py')) - print_colored('', '############# Test Executor #############', 'red') + print_colored('', '############# Test Microservice #############', 'red') user_query = ( general_guidelines() - + self.wrap_content_in_code_block(executor_content, 'executor.py', 'python') - + test_executor_file_task(executor_name, test) + + 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_executor_content_raw = conversation.query(user_query) + test_microservice_content_raw = conversation.query(user_query) if is_chain_of_thought: - test_executor_content_raw = conversation.query( + test_microservice_content_raw = conversation.query( f"General rules: " + not_allowed() + - chain_of_thought_optimization('python', 'test_executor.py') + chain_of_thought_optimization('python', 'test_microservice.py') + "Don't add any additional tests. " ) - test_executor_content = self.extract_content_from_result(test_executor_content_raw, 'test_executor.py') - persist_file(test_executor_content, os.path.join(EXECUTOR_FOLDER_v1, 'test_executor.py')) + test_microservice_content = self.extract_content_from_result(test_microservice_content_raw, 'test_microservice.py') + persist_file(test_microservice_content, os.path.join(MICROSERVICE_FOLDER_v1, 'test_microservice.py')) print_colored('', '############# Requirements #############', 'red') - requirements_path = os.path.join(EXECUTOR_FOLDER_v1, 'requirements.txt') + requirements_path = os.path.join(MICROSERVICE_FOLDER_v1, 'requirements.txt') user_query = ( general_guidelines() - + self.wrap_content_in_code_block(executor_content, 'executor.py', 'python') - + self.wrap_content_in_code_block(test_executor_content, 'test_executor.py', 'python') + + 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() @@ -124,8 +112,8 @@ class ExecutorFactory: print_colored('', '############# Dockerfile #############', 'red') user_query = ( general_guidelines() - + self.wrap_content_in_code_block(executor_content, 'executor.py', 'python') - + self.wrap_content_in_code_block(test_executor_content, 'test_executor.py', 'python') + + 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() ) @@ -135,28 +123,29 @@ class ExecutorFactory: dockerfile_content_raw = conversation.query( f"General rules: " + not_allowed() + chain_of_thought_optimization('dockerfile', 'Dockerfile')) dockerfile_content = self.extract_content_from_result(dockerfile_content_raw, 'Dockerfile') - persist_file(dockerfile_content, os.path.join(EXECUTOR_FOLDER_v1, 'Dockerfile')) + persist_file(dockerfile_content, os.path.join(MICROSERVICE_FOLDER_v1, 'Dockerfile')) - self.write_config_yml(executor_name, EXECUTOR_FOLDER_v1) - print('First version of the executor created. Start iterating on it to make the tests pass...') + 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...') - def create_playground(self, executor_name, executor_path, host): + def generate_playground(self, microservice_name, microservice_path): print_colored('', '############# Playground #############', 'red') - file_name_to_content = self.get_all_executor_files_with_content(executor_path) + 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['executor.py'], 'executor.py', 'python') - + self.wrap_content_in_code_block(file_name_to_content['test_executor.py'], 'test_executor.py', + + 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 {executor_name} using streamlit. +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. -The executor is hosted on {host}. +The playground must be started with a custom host: streamlit run app.py -- --host grpc://... +The playground must not let the user configure the --host grpc://... on the ui. 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}') +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 ''' @@ -166,25 +155,21 @@ print(response[0].text) # can also be blob in case of image/audio..., this shoul playground_content_raw = conversation.query( f"General rules: " + not_allowed() + chain_of_thought_optimization('python', 'app.py')) playground_content = self.extract_content_from_result(playground_content_raw, 'app.py') - persist_file(playground_content, os.path.join(executor_path, 'app.py')) + persist_file(playground_content, os.path.join(microservice_path, 'app.py')) - def get_executor_path(self, output_path, executor_name, package, num_approach, version): - package_path = '_'.join(package) - return os.path.join(output_path, executor_name, f'{num_approach}_{package_path}', f'v{version}') - def debug_executor(self, output_path, executor_name, num_approach, packages, description, test): - MAX_DEBUGGING_ITERATIONS = 10 + def debug_microservice(self, path, microservice_name, num_approach, packages, description, test): error_before = '' for i in range(1, MAX_DEBUGGING_ITERATIONS): print('Debugging iteration', i) print('Trying to build the microservice. Might take a while...') - previous_executor_path = self.get_executor_path(output_path, executor_name, packages, num_approach, i) - next_executor_path = self.get_executor_path(output_path, executor_name, packages, num_approach, i + 1) - log_hubble = push_executor(previous_executor_path) + 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) error = process_error_message(log_hubble) if error: - os.makedirs(next_executor_path) - file_name_to_content = self.get_all_executor_files_with_content(previous_executor_path) + os.makedirs(next_microservice_path) + file_name_to_content = get_all_microservice_files_with_content(previous_microservice_path) all_files_string = self.files_to_string(file_name_to_content) user_query = ( f"General rules: " + not_allowed() @@ -217,19 +202,19 @@ print(response[0].text) # can also be blob in case of image/audio..., this shoul file_name_to_content[file_name] = updated_file for file_name, content in file_name_to_content.items(): - persist_file(content, os.path.join(next_executor_path, file_name)) + persist_file(content, os.path.join(next_microservice_path, file_name)) error_before = error else: break if i == MAX_DEBUGGING_ITERATIONS - 1: - raise self.MaxDebugTimeReachedException('Could not debug the executor.') - return self.get_executor_path(output_path, executor_name, packages, num_approach, i) + raise self.MaxDebugTimeReachedException('Could not debug the microservice.') + return get_microservice_path(path, microservice_name, packages, num_approach, i) class MaxDebugTimeReachedException(BaseException): pass - def generate_executor_name(self, description): + def generate_microservice_name(self, description): conversation = self.gpt_session.get_conversation() user_query = f''' Generate a name for the executor matching the description: @@ -250,7 +235,7 @@ PDFParserExecutor name = self.extract_content_from_result(name_raw, 'name.txt') return name - def get_possible_packages(self, description, threads): + def get_possible_packages(self, description): print_colored('', '############# What package to use? #############', 'red') user_query = f''' Here is the task description of the problme you need to solve: @@ -279,29 +264,26 @@ package2,package3,... packages_raw = conversation.query(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[:threads] + packages = packages[:NUM_IMPLEMENTATION_STRATEGIES] return packages - def create(self, description, num_approaches, output_path, test): - generated_name = self.generate_executor_name(description) - executor_name = f'{generated_name}{random.randint(0, 1000_000)}' - packages_list = self.get_possible_packages(description, num_approaches) + def generate(self, description, test, microservice_path): + 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) for num_approach, packages in enumerate(packages_list): try: - self.create_executor(description, test, output_path, executor_name, packages, num_approach) - executor_path = self.debug_executor(output_path, executor_name, num_approach, packages, description, test) - host = jina_cloud.deploy_flow(executor_name, executor_path) - self.create_playground(executor_name, executor_path, host) + 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 Executor.') + print('Could not debug the Microservice.') continue print(f''' -Executor name: {executor_name} -Executor path: {executor_path} -Host: {host} - -Run the following command to start the playground: -streamlit run {os.path.join(executor_path, "app.py")} +You can now run or deploy your microservice: +gptdeploy run --path {microservice_path} +gptdeploy deploy --path {microservice_path} ''' ) break + diff --git a/src/prompt_system.py b/src/options/generate/prompt_system.py similarity index 95% rename from src/prompt_system.py rename to src/options/generate/prompt_system.py index 255c264..fe3eebb 100644 --- a/src/prompt_system.py +++ b/src/options/generate/prompt_system.py @@ -4,9 +4,8 @@ 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: -**executor.py** +**microservice.py** ```python -# this executor binary files as input and returns the length of each binary file as output from jina import Executor, requests, DocumentArray, Document import json class MyInfoExecutor(Executor): diff --git a/src/prompt_tasks.py b/src/options/generate/prompt_tasks.py similarity index 99% rename from src/prompt_tasks.py rename to src/options/generate/prompt_tasks.py index 5929901..ed67486 100644 --- a/src/prompt_tasks.py +++ b/src/options/generate/prompt_tasks.py @@ -50,7 +50,7 @@ def test_executor_file_task(executor_name, test_scenario): if test_scenario else "" ) + "Use the following import to import the executor: " - f"from executor import {executor_name} " + f"from microservice import {executor_name} " + not_allowed() + "The test must not open local files. " + "The test must not mock a function of the executor. " diff --git a/src/options/run/__init__.py b/src/options/run/__init__.py new file mode 100644 index 0000000..3ba2fb7 --- /dev/null +++ b/src/options/run/__init__.py @@ -0,0 +1 @@ +from src.options.run.runner import Runner \ No newline at end of file diff --git a/src/options/run/runner.py b/src/options/run/runner.py new file mode 100644 index 0000000..dcc6dbd --- /dev/null +++ b/src/options/run/runner.py @@ -0,0 +1,11 @@ +from src.apis.jina_cloud import run_locally +from src.options import validate_folder_is_correct, get_executor_name, get_latest_version_path + + +class Runner(): + def run(self, microservice_path): + validate_folder_is_correct(microservice_path) + executor_name = get_executor_name(microservice_path) + latest_version_path = get_latest_version_path(microservice_path) + run_locally(executor_name, latest_version_path) + diff --git a/src/server.py b/src/server.py deleted file mode 100644 index f103db5..0000000 --- a/src/server.py +++ /dev/null @@ -1,53 +0,0 @@ -# from fastapi import FastAPI -# from fastapi.exceptions import RequestValidationError -# from pydantic import BaseModel -# from typing import Optional, Dict -# -# from starlette.middleware.cors import CORSMiddleware -# from starlette.requests import Request -# from starlette.responses import JSONResponse -# from main import main -# -# app = FastAPI() -# -# # Define the request model -# class CreateRequest(BaseModel): -# test_scenario: str -# executor_description: str -# -# # Define the response model -# class CreateResponse(BaseModel): -# result: Dict[str, str] -# success: bool -# message: Optional[str] -# -# @app.post("/create", response_model=CreateResponse) -# def create_endpoint(request: CreateRequest): -# -# result = main( -# executor_description=request.executor_description, -# test_scenario=request.test_scenario, -# ) -# return CreateResponse(result=result, success=True, message=None) -# -# -# app.add_middleware( -# CORSMiddleware, -# allow_origins=["*"], -# allow_credentials=True, -# allow_methods=["*"], -# allow_headers=["*"], -# ) -# -# # Add a custom exception handler for RequestValidationError -# @app.exception_handler(RequestValidationError) -# def validation_exception_handler(request: Request, exc: RequestValidationError): -# return JSONResponse( -# status_code=422, -# content={"detail": exc.errors()}, -# ) -# -# -# if __name__ == "__main__": -# import uvicorn -# uvicorn.run("server:app", host="0.0.0.0", port=8000, log_level="info") diff --git a/src/utils/io.py b/src/utils/io.py index a9104f5..68f388d 100644 --- a/src/utils/io.py +++ b/src/utils/io.py @@ -1,17 +1,37 @@ import os -import shutil + import concurrent.futures import concurrent.futures from typing import Generator import sys from contextlib import contextmanager +import docker +from docker import APIClient + + +def get_microservice_path(path, microservice_name, package, num_approach, version): + package_path = '_'.join(package) + return os.path.join(path, microservice_name, f'{num_approach}_{package_path}', f'v{version}') def persist_file(file_content, file_path): with open(file_path, 'w') as f: f.write(file_content) +def get_all_microservice_files_with_content(folder_path): + file_name_to_content = {} + for filename in os.listdir(folder_path): + file_path = os.path.join(folder_path, filename) + + if os.path.isfile(file_path): + with open(file_path, 'r', encoding='utf-8') as file: + content = file.read() + file_name_to_content[filename] = content + + return file_name_to_content + + class GenerationTimeoutError(Exception): pass @@ -42,4 +62,19 @@ def suppress_stdout(): yield finally: sys.stdout.close() - sys.stdout = original_stdout \ No newline at end of file + sys.stdout = original_stdout + + +def is_docker_running(): + try: + from hubble import __windows__ + _client = docker.from_env() + # low-level client + _raw_client = APIClient( + base_url=docker.constants.DEFAULT_NPIPE + if __windows__ + else docker.constants.DEFAULT_UNIX_SOCKET + ) + except Exception: + return False + return True \ No newline at end of file diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/test_generator.py b/test/test_generator.py new file mode 100644 index 0000000..af33e33 --- /dev/null +++ b/test/test_generator.py @@ -0,0 +1,29 @@ +import unittest.mock as mock +from src.options.generate.generator import Generator +from src.apis.gpt import GPTSession + +def test_generator(tmpdir): + # Define a mock response + mock_response = { + "choices": [ + { + "delta": { + "content": "This is a mock response." + } + } + ] + } + + # Define a function to replace openai.ChatCompletion.create + def mock_create(*args, **kwargs): + return [mock_response] * kwargs.get("stream", 1) + + # Define a function to replace get_openai_api_key + def mock_get_openai_api_key(*args, **kwargs): + pass + + # Use mock.patch as a context manager to replace the original methods with the mocks + with mock.patch("openai.ChatCompletion.create", side_effect=mock_create), \ + mock.patch.object(GPTSession, "configure_openai_api_key", side_effect=mock_get_openai_api_key): + generator = Generator() + generator.generate("my description", "my test", str(tmpdir))