mirror of
https://github.com/aljazceru/dev-gpt.git
synced 2026-01-03 22:04:22 +01:00
Merge branch 'main' of https://github.com/jina-ai/gptdeploy into feat-gpt-turbo
# Conflicts: # src/executor_factory.py # src/gpt.py # src/prompt_system.py
This commit is contained in:
@@ -1,2 +1,2 @@
|
||||
__version__ = '0.18.11'
|
||||
__version__ = '0.18.15'
|
||||
from src.cli import main
|
||||
@@ -7,7 +7,7 @@ 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 recreate_folder, persist_file
|
||||
from src.utils.io import persist_file
|
||||
from src.utils.string_tools import print_colored
|
||||
|
||||
|
||||
@@ -72,10 +72,11 @@ class ExecutorFactory:
|
||||
output_path,
|
||||
executor_name,
|
||||
package,
|
||||
num_approach,
|
||||
is_chain_of_thought=False,
|
||||
):
|
||||
EXECUTOR_FOLDER_v1 = self.get_executor_path(output_path, package, 1)
|
||||
recreate_folder(EXECUTOR_FOLDER_v1)
|
||||
EXECUTOR_FOLDER_v1 = self.get_executor_path(output_path, executor_name, package, num_approach, 1)
|
||||
os.makedirs(EXECUTOR_FOLDER_v1)
|
||||
|
||||
print_colored('', '############# Executor #############', 'red')
|
||||
user_query = (
|
||||
@@ -175,42 +176,32 @@ Please provide the complete file with the exact same syntax to wrap the code.
|
||||
playground_content = self.extract_content_from_result(playground_content_raw, 'app.py', match_single_block=True)
|
||||
persist_file(playground_content, os.path.join(executor_path, 'app.py'))
|
||||
|
||||
def get_executor_path(self, output_path, package, version):
|
||||
def get_executor_path(self, output_path, executor_name, package, num_approach, version):
|
||||
package_path = '_'.join(package)
|
||||
return os.path.join(output_path, package_path, f'v{version}')
|
||||
return os.path.join(output_path, executor_name, f'{num_approach}_{package_path}', f'v{version}')
|
||||
|
||||
def debug_executor(self, output_path, package, description, test):
|
||||
def debug_executor(self, output_path, executor_name, num_approach, packages, description, test):
|
||||
MAX_DEBUGGING_ITERATIONS = 10
|
||||
error_before = ''
|
||||
# conversation = self.gpt_session.get_conversation()
|
||||
for i in range(1, MAX_DEBUGGING_ITERATIONS):
|
||||
print('Debugging iteration', i)
|
||||
previous_executor_path = self.get_executor_path(output_path, package, i)
|
||||
next_executor_path = self.get_executor_path(output_path, package, i + 1)
|
||||
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)
|
||||
error = process_error_message(log_hubble)
|
||||
if error:
|
||||
recreate_folder(next_executor_path)
|
||||
os.makedirs(next_executor_path)
|
||||
file_name_to_content = self.get_all_executor_files_with_content(previous_executor_path)
|
||||
|
||||
is_dependency_issue = self.is_dependency_issue(error, file_name_to_content['Dockerfile'])
|
||||
print(f'Current error is a dependency issue: {is_dependency_issue}')
|
||||
|
||||
if is_dependency_issue:
|
||||
all_files_string = self.files_to_string({
|
||||
key: val for key, val in file_name_to_content.items() if key in ['requirements.txt', 'Dockerfile']
|
||||
})
|
||||
# user_query = (
|
||||
# f'I have the following files:\n{all_files_string}\n\n'
|
||||
# + f'This error happens during the docker build process:\n{error}\n\n'
|
||||
# + 'First, think about what kind of error is this? Look at exactly at the stack trace and then '
|
||||
# "suggest how to solve it. Output the files that need change. "
|
||||
# "Don't output files that don't need change. If you output a file, then write the "
|
||||
# "complete file. Use the exact same syntax to wrap the code:\n"
|
||||
# f"**...**\n"
|
||||
# f"```...\n"
|
||||
# f"...code...\n"
|
||||
# f"```"
|
||||
# )
|
||||
user_query = (
|
||||
f"Your task is to provide guidance on how to solve an error that occurred during the Docker "
|
||||
f"build process. The error message is:\n{error}\nTo solve this error, you should first "
|
||||
@@ -221,7 +212,6 @@ Please provide the complete file with the exact same syntax to wrap the code.
|
||||
f"You are given the following files:\n\n{all_files_string}"
|
||||
)
|
||||
else:
|
||||
# if i == 1:
|
||||
all_files_string = self.files_to_string(file_name_to_content)
|
||||
user_query = (
|
||||
f"General rules: " + not_allowed()
|
||||
@@ -229,8 +219,9 @@ Please provide the complete file with the exact same syntax to wrap the code.
|
||||
+ f'\n\nHere is the test scenario the executor must pass:\n{test}'
|
||||
+ f'Here are all the files I use:\n{all_files_string}'
|
||||
+ f'\n\nThis error happens during the docker build process:\n{error}\n\n'
|
||||
+ 'First, think about what kind of error is this? Look at exactly at the stack trace and then '
|
||||
"suggest how to solve it. Output the files that need change. "
|
||||
+ 'Look at exactly at the stack trace. First, think about what kind of error is this? '
|
||||
'Then think about possible reasons which might have caused it. Then suggest how to '
|
||||
'solve it. Output the files that need change. '
|
||||
"Don't output files that don't need change. If you output a file, then write the "
|
||||
"complete file. Use the exact same syntax to wrap the code:\n"
|
||||
f"**...**\n"
|
||||
@@ -238,9 +229,6 @@ Please provide the complete file with the exact same syntax to wrap the code.
|
||||
f"...code...\n"
|
||||
f"```"
|
||||
)
|
||||
# else:
|
||||
# conversation.set_system_definition()
|
||||
# user_query = f'Now this error happens during the docker build process:\n{error}'
|
||||
|
||||
conversation = self.gpt_session.get_conversation()
|
||||
returned_files_raw = conversation.query(user_query)
|
||||
@@ -251,13 +239,13 @@ Please provide the complete file with the exact same syntax to wrap the code.
|
||||
|
||||
for file_name, content in file_name_to_content.items():
|
||||
persist_file(content, os.path.join(next_executor_path, file_name))
|
||||
error_before = error_before + '\n' + error
|
||||
error_before = error_before
|
||||
|
||||
else:
|
||||
break
|
||||
if i == MAX_DEBUGGING_ITERATIONS - 1:
|
||||
raise self.MaxDebugTimeReachedException('Could not debug the executor.')
|
||||
return self.get_executor_path(output_path, package, i)
|
||||
return self.get_executor_path(output_path, executor_name, packages, num_approach, i)
|
||||
|
||||
class MaxDebugTimeReachedException(BaseException):
|
||||
pass
|
||||
@@ -329,22 +317,22 @@ package2,package3,...
|
||||
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)
|
||||
recreate_folder(output_path)
|
||||
for packages in packages_list:
|
||||
for num_approach, packages in enumerate(packages_list):
|
||||
try:
|
||||
self.create_executor(description, test, output_path, executor_name, packages)
|
||||
executor_path = self.debug_executor(output_path, packages, description, test)
|
||||
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)
|
||||
except self.MaxDebugTimeReachedException:
|
||||
print('Could not debug the executor.')
|
||||
print('Could not debug the Executor.')
|
||||
continue
|
||||
print(f'''
|
||||
Executor name: {executor_name}
|
||||
Executor path: {executor_path}
|
||||
Host: {host}
|
||||
|
||||
Playground: streamlit run {os.path.join(executor_path, "app.py")}
|
||||
Run the following command to start the playground:
|
||||
streamlit run {os.path.join(executor_path, "app.py")}
|
||||
'''
|
||||
)
|
||||
break
|
||||
|
||||
15
src/gpt.py
15
src/gpt.py
@@ -19,7 +19,7 @@ class GPTSession:
|
||||
self.supported_model = 'gpt-4'
|
||||
self.pricing_prompt = PRICING_GPT4_PROMPT
|
||||
self.pricing_generation = PRICING_GPT4_GENERATION
|
||||
elif (model == 'gpt-4' and not self.is_gpt4_available()) or model == 'gpt-3.5-turbo':
|
||||
else:
|
||||
if model == 'gpt-4':
|
||||
print_colored('GPT-4 is not available. Using GPT-3.5-turbo instead.', 'yellow')
|
||||
model = 'gpt-3.5-turbo'
|
||||
@@ -31,7 +31,11 @@ class GPTSession:
|
||||
|
||||
def get_openai_api_key(self):
|
||||
if 'OPENAI_API_KEY' not in os.environ:
|
||||
raise Exception('You need to set OPENAI_API_KEY in your environment')
|
||||
raise Exception('''
|
||||
You need to set OPENAI_API_KEY in your environment.
|
||||
If you have updated it already, please restart your terminal.
|
||||
'''
|
||||
)
|
||||
openai.api_key = os.environ['OPENAI_API_KEY']
|
||||
|
||||
def is_gpt4_available(self):
|
||||
@@ -42,9 +46,10 @@ class GPTSession:
|
||||
model="gpt-4",
|
||||
messages=[{
|
||||
"role": 'system',
|
||||
"content": 'test'
|
||||
"content": 'you respond nothing'
|
||||
}]
|
||||
)
|
||||
break
|
||||
except RateLimitError:
|
||||
sleep(1)
|
||||
continue
|
||||
@@ -61,7 +66,7 @@ class GPTSession:
|
||||
print('Estimated costs on openai.com:')
|
||||
# print('money prompt:', f'${money_prompt}')
|
||||
# print('money generation:', f'${money_generation}')
|
||||
print('total money so far:', f'${money_prompt + money_generation}')
|
||||
print('total money spent so far:', f'${money_prompt + money_generation}')
|
||||
print('\n')
|
||||
|
||||
def get_conversation(self, system_definition_examples: List[str] = ['executor', 'docarray', 'client']):
|
||||
@@ -100,7 +105,7 @@ class _GPTConversation:
|
||||
delta = chunk['choices'][0]['delta']
|
||||
if 'content' in delta:
|
||||
content = delta['content']
|
||||
print_colored('' if complete_string else 'assistent', content, 'green', end='')
|
||||
print_colored('' if complete_string else 'assistant', content, 'green', end='')
|
||||
complete_string += content
|
||||
return complete_string
|
||||
|
||||
|
||||
@@ -10,6 +10,9 @@ import hubble
|
||||
from hubble.executor.helper import upload_file, archive_package, get_request_header
|
||||
from jcloud.flow import CloudFlow
|
||||
|
||||
from src.utils.io import suppress_stdout
|
||||
from src.utils.string_tools import print_colored
|
||||
|
||||
|
||||
def redirect_callback(href):
|
||||
print(
|
||||
@@ -23,6 +26,12 @@ def jina_auth_login():
|
||||
try:
|
||||
hubble.Client(jsonify=True).get_user_info(log_error=False)
|
||||
except hubble.AuthenticationRequiredError:
|
||||
print('You need login to Jina first to use GPTDeploy')
|
||||
print_colored('', '''
|
||||
If you just created an account, it can happen that the login callback is not working.
|
||||
In this case, please cancel this run, rerun your gptdeploy command and login into your account again.
|
||||
''', 'green'
|
||||
)
|
||||
hubble.login(prompt='login', redirect_callback=redirect_callback)
|
||||
|
||||
|
||||
@@ -41,7 +50,9 @@ def push_executor(dir_path):
|
||||
'verbose': 'True',
|
||||
'md5sum': md5_digest,
|
||||
}
|
||||
req_header = get_request_header()
|
||||
with suppress_stdout():
|
||||
req_header = get_request_header()
|
||||
|
||||
resp = upload_file(
|
||||
'https://api.hubble.jina.ai/v2/rpc/executor.push',
|
||||
'filename',
|
||||
|
||||
@@ -80,8 +80,12 @@ def set_env_variable(shell, key):
|
||||
with open(config_file, "a") as file:
|
||||
file.write(f"\n{export_line}\n")
|
||||
|
||||
click.echo(
|
||||
f"✅ Success, OPENAI_API_KEY has been set in {config_file}\nPlease restart your shell to apply the changes.")
|
||||
click.echo(f'''
|
||||
✅ Success, OPENAI_API_KEY has been set in {config_file}.
|
||||
Please restart your shell to apply the changes or run:
|
||||
source {config_file}
|
||||
'''
|
||||
)
|
||||
|
||||
except FileNotFoundError:
|
||||
click.echo(f"Error: {config_file} not found. Please set the environment variable manually.")
|
||||
@@ -93,7 +97,12 @@ def set_api_key(key):
|
||||
if system_platform == "windows":
|
||||
set_env_variable_command = f'setx OPENAI_API_KEY "{key}"'
|
||||
subprocess.call(set_env_variable_command, shell=True)
|
||||
click.echo("✅ Success, OPENAI_API_KEY has been set.\nPlease restart your Command Prompt to apply the changes.")
|
||||
click.echo('''
|
||||
✅ Success, OPENAI_API_KEY has been set.
|
||||
Please restart your Command Prompt to apply the changes.
|
||||
'''
|
||||
)
|
||||
|
||||
elif system_platform in ["linux", "darwin"]:
|
||||
if "OPENAI_API_KEY" in os.environ or is_key_set_in_config_file(key):
|
||||
if not click.confirm("OPENAI_API_KEY is already set. Do you want to overwrite it?"):
|
||||
|
||||
@@ -7,83 +7,52 @@ Here is an example of how an executor can be defined. It always starts with a co
|
||||
```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):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__()
|
||||
|
||||
@requests() # each executor must have exactly this decorator without parameters
|
||||
@requests() # each Executor must have exactly this decorator without parameters
|
||||
def foo(self, docs: DocumentArray, **kwargs) => DocumentArray:
|
||||
for d in docs:
|
||||
d.load_uri_to_blob()
|
||||
d.blob = None
|
||||
content = json.loads(d.text)
|
||||
...
|
||||
d.text = json.dumps(modified_content)
|
||||
return docs
|
||||
```
|
||||
|
||||
An executor gets a DocumentArray as input and returns a DocumentArray as output.'''
|
||||
An Executor gets a DocumentArray as input and returns a DocumentArray as output.
|
||||
'''
|
||||
|
||||
docarray_example = f'''A DocumentArray is a python class that can be seen as a list of Documents.
|
||||
A Document is a python class that represents a single document.
|
||||
Here is the protobuf definition of a Document:
|
||||
|
||||
message DocumentProto {{
|
||||
// A hexdigest that represents a unique document ID
|
||||
string id = 1;
|
||||
|
||||
oneof content {{
|
||||
// the raw binary content of this document, which often represents the original document when comes into jina
|
||||
bytes blob = 2;
|
||||
|
||||
// the ndarray of the image/audio/video document
|
||||
NdArrayProto tensor = 3;
|
||||
|
||||
// a text document
|
||||
string text = 4;
|
||||
}}
|
||||
|
||||
// a uri of the document is a remote url starts with http or https or data URI scheme
|
||||
string uri = 5;
|
||||
|
||||
// list of the sub-documents of this document (recursive structure)
|
||||
repeated DocumentProto chunks = 6;
|
||||
|
||||
// the matched documents on the same level (recursive structure)
|
||||
repeated DocumentProto matches = 7;
|
||||
|
||||
// the embedding of this document
|
||||
NdArrayProto embedding = 8;
|
||||
// used to store json data the executor gets and returns
|
||||
string text = 1;
|
||||
}}
|
||||
|
||||
Here is an example of how a DocumentArray can be defined:
|
||||
Here are examples of how a DocumentArray can be defined:
|
||||
|
||||
from jina import DocumentArray, Document
|
||||
import json
|
||||
|
||||
d1 = Document(text='hello')
|
||||
d1 = Document(text=json.dumps({{'he_says': 'hello'}}))
|
||||
|
||||
# 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\
|
||||
base64_data = base64.b64encode(png_data).decode('utf-8')
|
||||
d2 = Document(text=json.dumps({{'image': base64_data}}))
|
||||
|
||||
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',
|
||||
)
|
||||
d5 = Document()
|
||||
d5.tensor = np.ones((2,4))
|
||||
d5.uri = 'https://audio.com/audio.mp3'
|
||||
d6 = Document()
|
||||
d6.blob # like b'RIFF\\x00\\x00\\x00\\x00WAVEfmt \\x10\\x00...'
|
||||
docs = DocumentArray([
|
||||
d1, d2, d3, d4
|
||||
])
|
||||
d7 = Document()
|
||||
d7.text = 'test string'
|
||||
d8 = Document()
|
||||
d8.text = json.dumps([{{"id": "1", "text": ["hello", 'test']}}, {{"id": "2", "text": "world"}}])
|
||||
# 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... '''
|
||||
array = numpy.array([1, 2, 3])
|
||||
array_list = array.tolist()
|
||||
d3 = Document(text=json.dumps(array_list))
|
||||
d4 = Document()
|
||||
d4.text = '{{"uri": "https://.../logo.png"}}'
|
||||
'''
|
||||
|
||||
|
||||
client_example = f'''After the executor is deployed, it can be called via Jina Client.
|
||||
|
||||
@@ -9,7 +9,7 @@ def general_guidelines():
|
||||
"Every file starts with comments describing what the code is doing before the first import. "
|
||||
"Comments can only be written within code blocks. "
|
||||
"Then all imports are listed. "
|
||||
"It is important to import all modules that could be needed in the executor code. "
|
||||
"It is important to import all modules that could be needed in the Executor code. "
|
||||
"Always import: "
|
||||
"from jina import Executor, DocumentArray, Document, requests "
|
||||
"Start from top-level and then fully implement all methods. "
|
||||
@@ -143,5 +143,5 @@ The executor must not access external apis except unless it is explicitly mentio
|
||||
The executor must not load data from the local file system unless it was created by the executor itself.
|
||||
The executor must not use a pre-trained model unless it is explicitly mentioned in the description.
|
||||
The executor must not train a model.
|
||||
The executor must not use Document.tags.
|
||||
The executor must not use any attribute of Document accept Document.text.
|
||||
'''
|
||||
@@ -3,14 +3,12 @@ import shutil
|
||||
import concurrent.futures
|
||||
import concurrent.futures
|
||||
from typing import Generator
|
||||
import sys
|
||||
from contextlib import contextmanager
|
||||
|
||||
def recreate_folder(folder_path):
|
||||
if os.path.exists(folder_path) and os.path.isdir(folder_path):
|
||||
shutil.rmtree(folder_path)
|
||||
os.makedirs(folder_path)
|
||||
|
||||
def persist_file(file_content, file_name):
|
||||
with open(f'{file_name}', 'w') as f:
|
||||
def persist_file(file_content, file_path):
|
||||
with open(file_path, 'w') as f:
|
||||
f.write(file_content)
|
||||
|
||||
|
||||
@@ -34,4 +32,14 @@ def timeout_generator_wrapper(generator, timeout):
|
||||
except concurrent.futures.TimeoutError:
|
||||
raise GenerationTimeoutError(f"Generation took longer than {timeout} seconds")
|
||||
|
||||
return wrapper()
|
||||
return wrapper()
|
||||
|
||||
@contextmanager
|
||||
def suppress_stdout():
|
||||
original_stdout = sys.stdout
|
||||
sys.stdout = open(os.devnull, 'w')
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
sys.stdout.close()
|
||||
sys.stdout = original_stdout
|
||||
Reference in New Issue
Block a user