Merge branch 'main' of https://github.com/jina-ai/gptdeploy into refactor-langchain

# Conflicts:
#	src/apis/gpt.py
#	src/cli.py
#	src/options/generate/generator.py
#	src/options/generate/prompt_system.py
#	src/options/generate/prompt_tasks.py
This commit is contained in:
Joschka Braun
2023-04-18 12:17:52 +02:00
15 changed files with 112 additions and 85 deletions

View File

@@ -100,54 +100,67 @@ jc delete <microservice id>
<img src="res/teaser.png" alt="QR Code Generator" width="600" />
### Chemical Formula Visualization
```bash
generate --description "Convert a chemical formula into a 2D chemical structure diagram" --test "C=C, CN=C=O, CCC(=O)O" --path microservice
```
<img src="res/chemical_formula_example.png" alt="Chemical Formula Visualization" width="600" />
### Animal Detector
```bash
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" --model gpt-4
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" --model gpt-4 --path microservice
```
<img src="res/animal_detector_example.png" alt="Animal Detector" width="600" />
### Meme Generator
```bash
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" --model gpt-4
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" --model gpt-4 --path microservice
```
<img src="res/meme_example.png" alt="Meme Generator" width="600" />
### Rhyme Generator
```bash
gptdeploy generate --description "Given a word, return a list of rhyming words using the datamuse api" --test "hello" --model gpt-4
gptdeploy generate --description "Given a word, return a list of rhyming words using the datamuse api" --test "hello" --model gpt-4 --path microservice
```
<img src="res/rhyme_generator_example.png" alt="Rhyme Generator" width="600" />
### Word Cloud Generator
```bash
gptdeploy generate --description "Generate a word cloud from a given text" --test "Lorem ipsum dolor sit amet, consectetur adipiscing elit." --model gpt-4
gptdeploy generate --description "Generate a word cloud from a given text" --test "Lorem ipsum dolor sit amet, consectetur adipiscing elit." --model gpt-4 --path microservice
```
<img src="res/word_cloud_example.png" alt="Word Cloud Generator" width="600" />
### 3d model info
```bash
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" --model gpt-4
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" --model gpt-4 --path microservice
```
<img src="res/obj_info_example.png" alt="3D Model Info" width="600" />
### 2d rendering of 3d model
```bash
gptdeploy generate --description "create a 2d rendering of a whole 3d object and x,y,z object rotation using trimesh and pyrender.OffscreenRenderer with os.environ['PYOPENGL_PLATFORM'] = 'egl' and freeglut3-dev library" --test "input: https://graphics.stanford.edu/courses/cs148-10-summer/as3/code/as3/teapot.obj output: assert the image is not completely white or black" --model gpt-4 --path microservice
```
<img src="res/obj_render_example.gif" alt="2D Rendering of 3D Model" width="600" />
### Table extraction
```bash
gptdeploy generate --description "Given a URL, extract all tables as csv" --test "http://www.ins.tn/statistiques/90" --model gpt-4
gptdeploy generate --description "Given a URL, extract all tables as csv" --test "http://www.ins.tn/statistiques/90" --model gpt-4 --path microservice
```
<img src="res/table_extraction_example.png" alt="Table Extraction" width="600" />
### Audio to mel spectrogram
```bash
gptdeploy generate --description "Create mel spectrograms from audio file" --test "https://cdn.pixabay.com/download/audio/2023/02/28/audio_550d815fa5.mp3" --model gpt-4
gptdeploy generate --description "Create mel spectrograms from audio file" --test "https://cdn.pixabay.com/download/audio/2023/02/28/audio_550d815fa5.mp3" --model gpt-4 --path microservice
```
<img src="res/audio_to_mel_example.png" alt="Audio to Mel Spectrogram" width="600" />
### Text to speech
```bash
gptdeploy generate --description "Convert text to speech" --test "Hello, welcome to GPT Deploy!" --model gpt-4
gptdeploy generate --description "Convert text to speech" --test "Hello, welcome to GPT Deploy!" --model gpt-4 --path microservice
```
<a href=res/text_to_speech_example.wav><img src="res/text_to_speech_example.png" alt="Text to Speech" width="600" /></a>
@@ -158,20 +171,20 @@ gptdeploy generate --description "Convert text to speech" --test "Hello, welcome
### Heatmap Generator
```bash
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]]" --model gpt-4
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]]" --model gpt-4 --path microservice
```
<img src="res/heatmap_example.png" alt="Heatmap Generator" width="600" />
### QR Code Generator
```bash
gptdeploy generate --description "Generate QR code from URL" --test "https://www.example.com" --model gpt-4
gptdeploy generate --description "Generate QR code from URL" --test "https://www.example.com" --model gpt-4 --path microservice
```
<img src="res/qr_example.png" alt="QR Code Generator" width="600" />
### Mandelbrot Set Visualizer
```bash
gptdeploy generate --description "Visualize the Mandelbrot set with custom parameters" --test "center=-0+1i, zoom=1.0, size=800x800, iterations=1000" --model gpt-4
gptdeploy generate --description "Visualize the Mandelbrot set with custom parameters" --test "center=-0+1i, zoom=1.0, size=800x800, iterations=1000" --model gpt-4 --path microservice
```
<img src="res/mandelbrot_example.png" alt="Mandelbrot Set Visualizer" width="600" />
@@ -341,14 +354,6 @@ gptdeploy generate --description "Visualize the Mandelbrot set with custom param
[//]: # (## Upcoming Challenges)
[//]: # (### Chemical Structure Drawing)
[//]: # (```bash)
[//]: # (gptdeploy generate --description "Convert a chemical formula into a 2D chemical structure diagram" --test "C6H6")
[//]: # (```)
[//]: # (### Color Palette Generator)
[//]: # (```bash)

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

BIN
res/obj_render_example.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 898 KiB

View File

@@ -7,7 +7,7 @@ def read_requirements():
setup(
name='gptdeploy',
version='0.18.16',
version='0.18.18',
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',

View File

@@ -1,2 +1,2 @@
__version__ = '0.18.16'
__version__ = '0.18.18'
from src.cli import main

View File

@@ -15,7 +15,9 @@ from src.utils.string_tools import print_colored
class GPTSession:
def __init__(self, model: str = 'gpt-4'):
def __init__(self, task_description, test_description, model: str = 'gpt-4', ):
self.task_description = task_description
self.test_description = test_description
self.configure_openai_api_key()
self.model_name = 'gpt-4' if model == 'gpt-4' and self.is_gpt4_available() else 'gpt-3.5-turbo'
@@ -51,7 +53,7 @@ If you have updated it already, please restart your terminal.
return False
def get_conversation(self, system_definition_examples: List[str] = ['executor', 'docarray', 'client']):
return _GPTConversation(self.model_name, system_definition_examples)
return _GPTConversation(self.model_name, self.task_description, self.test_description, system_definition_examples)
class AssistantStreamingStdOutCallbackHandler(StreamingStdOutCallbackHandler):
@@ -62,7 +64,7 @@ class AssistantStreamingStdOutCallbackHandler(StreamingStdOutCallbackHandler):
class _GPTConversation:
def __init__(self, model: str, system_definition_examples: List[str] = ['executor', 'docarray', 'client']):
def __init__(self, model: str, task_description, test_description, system_definition_examples: List[str] = ['executor', 'docarray', 'client']):
self.chat = ChatOpenAI(
model_name=model,
streaming=True,
@@ -85,7 +87,7 @@ class _GPTConversation:
return response
@staticmethod
def _create_system_message(system_definition_examples: List[str] = []) -> SystemMessage:
def _create_system_message(task_description, test_description, system_definition_examples: List[str] = []) -> SystemMessage:
system_message = system_message_base
if 'executor' in system_definition_examples:
system_message += f'\n{executor_example}'
@@ -93,4 +95,5 @@ class _GPTConversation:
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)

View File

@@ -248,10 +248,18 @@ def update_client_line_in_file(file_path, host):
file.write(replaced_content)
def remove_after_stderr(relevant_lines):
def shorten_logs(relevant_lines):
# handle duplicate error messages
for index, line in enumerate(relevant_lines):
if '--- Captured stderr call ----' in line:
return relevant_lines[:index]
relevant_lines = relevant_lines[:index]
# filter pip install logs
relevant_lines = [line for line in relevant_lines if ' Requirement already satisfied: ' not in line]
# filter version not found logs
for index, line in enumerate(relevant_lines):
if 'ERROR: Could not find a version that satisfies the requirement ' in line:
start_and_end = line[:150] + '...' + line[-150:]
relevant_lines[index] = start_and_end
return relevant_lines
@@ -270,9 +278,9 @@ def process_error_message(error_message):
if last_matching_line_index is not None:
relevant_lines = lines[last_matching_line_index:]
relevant_lines = remove_after_stderr(relevant_lines)
relevant_lines = shorten_logs(relevant_lines)
response = '\n'.join(relevant_lines[-25:]).strip()
response = '\n'.join(relevant_lines[-100:]).strip()
# the following code tests the case that the docker file is corrupted and can not be parsed
# the method above will not return a relevant error message in this case
@@ -282,21 +290,3 @@ def process_error_message(error_message):
if not response and last_line.startswith('error: '):
return last_line
return response
def build_docker(path):
# The command to build the Docker image
cmd = f"docker build -t micromagic {path}"
# Run the command and capture the output
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
stdout, stderr = process.communicate()
# Check if there was an error
if process.returncode != 0:
error_message = stderr.decode("utf-8")
relevant_error_message = process_error_message(error_message)
return relevant_error_message
else:
print("Docker build completed successfully.")
return ''

View File

@@ -64,13 +64,12 @@ def generate(
return
from src.options.generate.generator import Generator
generator = Generator(model=model)
generator = Generator(description, test, model=model)
with get_openai_callback() as cb:
generator.generate(description, test, path)
generator.generate(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
def run(path):

View File

@@ -30,5 +30,6 @@ MAX_DEBUGGING_ITERATIONS = 10
DEMO_TOKEN = '45372338e04f5a41af949024db929d46'
PROBLEMATIC_PACKAGES = [
'Pyrender', 'Trimesh', 'ModernGL', 'PyOpenGL', 'Pyglet', 'pythreejs', 'panda3d' # because they need a screen
# 'Pyrender', 'Trimesh',
'ModernGL', 'PyOpenGL', 'Pyglet', 'pythreejs', 'panda3d' # because they need a screen
]

View File

@@ -1,13 +1,19 @@
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_folder(path, max_fn=max):
return max_fn([os.path.join(path, f) for f in os.listdir(path) if os.path.isdir(os.path.join(path, f))])
def version_max_fn(path_list):
version_list = [int(path.split('/')[-1].replace('v', '')) for path in path_list]
max_version = max(version_list)
max_index = version_list.index(max_version)
return path_list[max_index]
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)
latest_version_path = get_latest_folder(latest_approach_path, max_fn=version_max_fn)
return latest_version_path
def get_executor_name(microservice_path):

View File

@@ -17,21 +17,22 @@ from src.utils.string_tools import print_colored
class Generator:
def __init__(self, model='gpt-4'):
self.gpt_session = gpt.GPTSession(model=model)
def __init__(self, task_description, test_description, model='gpt-4'):
self.gpt_session = gpt.GPTSession(task_description, test_description, model=model)
self.task_description = task_description
self.test_description = test_description
def extract_content_from_result(self, plain_text, file_name, match_single_block=False):
pattern = fr"^\*\*{file_name}\*\*\n```(?:\w+\n)?([\s\S]*?)```"
match = re.search(pattern, plain_text, re.MULTILINE)
if match:
return match.group(1).strip()
else:
elif match_single_block:
# Check for a single code block
single_code_block_pattern = r"^```(?:\w+\n)?([\s\S]*?)```"
single_code_block_match = re.findall(single_code_block_pattern, plain_text, re.MULTILINE)
if match_single_block and len(single_code_block_match) == 1:
if len(single_code_block_match) == 1:
return single_code_block_match[0].strip()
else:
return ''
def write_config_yml(self, microservice_name, dest_folder):
@@ -58,10 +59,10 @@ metas:
test,
path,
microservice_name,
package,
packages,
num_approach,
):
MICROSERVICE_FOLDER_v1 = get_microservice_path(path, microservice_name, package, num_approach, 1)
MICROSERVICE_FOLDER_v1 = get_microservice_path(path, microservice_name, packages, num_approach, 1)
os.makedirs(MICROSERVICE_FOLDER_v1)
print_colored('', '############# Microservice #############', 'blue')
@@ -168,7 +169,6 @@ metas:
persist_file(playground_content, os.path.join(microservice_path, 'app.py'))
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...')
@@ -178,20 +178,22 @@ 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, error_before, next_microservice_path,
self.do_debug_iteration(description, error, next_microservice_path,
previous_microservice_path, test)
error_before = error
if i == MAX_DEBUGGING_ITERATIONS - 1:
raise self.MaxDebugTimeReachedException('Could not debug the microservice.')
else:
print('Successfully build microservice.')
break
if i == MAX_DEBUGGING_ITERATIONS - 1:
raise self.MaxDebugTimeReachedException('Could not debug the microservice.')
return get_microservice_path(path, microservice_name, packages, num_approach, i)
def do_debug_iteration(self, description, error, error_before, next_microservice_path, previous_microservice_path,
def do_debug_iteration(self, description, error, next_microservice_path, previous_microservice_path,
test):
os.makedirs(next_microservice_path)
file_name_to_content = get_all_microservice_files_with_content(previous_microservice_path)
summarized_error = self.summarize_error(error)
is_dependency_issue = self.is_dependency_issue(error, file_name_to_content['Dockerfile'])
if is_dependency_issue:
all_files_string = self.files_to_string({
@@ -199,11 +201,11 @@ metas:
key in ['requirements.txt', 'Dockerfile']
})
user_query = template_solve_dependency_issue.format(
description=description, error=error, all_files_string=all_files_string,
description=description, summarized_error=summarized_error, all_files_string=all_files_string,
)
else:
user_query = template_solve_code_issue.format(
description=description, error=error, all_files_string=self.files_to_string(file_name_to_content),
description=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)
@@ -211,6 +213,7 @@ metas:
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']):
file_name_to_content[file_name] = updated_file
print(f'Updated {file_name}')
for file_name, content in file_name_to_content.items():
persist_file(content, os.path.join(next_microservice_path, file_name))
@@ -240,12 +243,12 @@ metas:
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')]
packages = packages[:NUM_IMPLEMENTATION_STRATEGIES]
return packages
packages_list = [[pkg.strip() for pkg in packages_string.split(',')] for packages_string in packages_csv_string.split('\n')]
packages_list = packages_list[:NUM_IMPLEMENTATION_STRATEGIES]
return packages_list
def generate(self, description, test, microservice_path):
generated_name = self.generate_microservice_name(description)
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 = [
@@ -254,7 +257,7 @@ metas:
for num_approach, packages in enumerate(packages_list):
try:
self.generate_microservice(
description, test, microservice_path, microservice_name, packages, num_approach
self.task_description, self.test_description, microservice_path, microservice_name, packages, num_approach
)
final_version_path = self.debug_microservice(
microservice_path, microservice_name, num_approach, packages, description, test
@@ -274,3 +277,14 @@ gptdeploy deploy --path {microservice_path}
'''
)
break
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)
return error_summary

View File

@@ -16,7 +16,7 @@ class MyInfoExecutor(Executor):
for d in docs:
content = json.loads(d.text)
...
d.text = json.dumps(modified_content)
d.text = json.dumps(modified_content) # serialized json
return docs
```
@@ -28,7 +28,7 @@ A Document is a python class that represents a single document.
Here is the protobuf definition of a Document:
```
message DocumentProto {{
// used to store json data the executor gets and returns
// used to store serialized json data the executor gets and returns
string text = 1;
}}
```
@@ -71,7 +71,16 @@ print(response[0].text)
```'''
system_message_base = '''It is the year 2021.
system_base_definition = '''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.
Your goal is to build a microservice that: {description}'''
To be more specific, you help the user to build a microservice with the following requirements:
```
{task_description}
```
and the following test scenario:
```
{test_description}
```
You must obey the following rules:''' + f'\n{not_allowed_executor}\n{not_allowed_docker}'

View File

View File

@@ -9,8 +9,8 @@ import docker
from docker import APIClient
def get_microservice_path(path, microservice_name, package, num_approach, version):
package_path = '_'.join(package)
def get_microservice_path(path, microservice_name, packages, num_approach, version):
package_path = '_'.join(packages)
return os.path.join(path, microservice_name, f'{num_approach}_{package_path}', f'v{version}')
def persist_file(file_content, file_path):

View File

@@ -25,5 +25,5 @@ def test_generator(tmpdir):
# 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))
generator = Generator("my description", "my test")
generator.generate(str(tmpdir))