Skip to content

Commit

Permalink
[apps/Hal9] Multiple Tools loop in main, Add Arbitrary code execution…
Browse files Browse the repository at this point in the history
… and other fixes (#470)

[apps/Hal9 Multiple Tools loop in main, Add Arbitrary code execution and other fixes]
  • Loading branch information
LuisGuillen03 authored Feb 10, 2025
1 parent 547b23e commit bfc330e
Show file tree
Hide file tree
Showing 11 changed files with 141 additions and 55 deletions.
41 changes: 19 additions & 22 deletions apps/hal9/app.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
from utils import generate_response, load_messages, insert_message, execute_function, save_messages, insert_tool_message, is_url, download_file, generate_text_embeddings_parquet
from tools.calculator import solve_math_problem_description, solve_math_problem
from tools.generic import answer_generic_question, answer_generic_question_description, answer_generic_future
from tools.generic import answer_generic_question_description, answer_generic_question
from tools.csv_agent import analyze_csv_description, analyze_csv
from tools.image_agent import images_management_system, images_management_system_description, add_images_descriptions
from tools.hal9 import answer_hal9_questions_description, answer_hal9_questions
from tools.text_agent import analyze_text_file_description, analyze_text_file
from tools.streamlit import streamlit_generator, streamlit_generator_description
from tools.website import website_generator, website_generator_description
from concurrent.futures import ThreadPoolExecutor
from tools.other_tools import final_response_description, final_response
from tools.python_execution import python_execution_description ,python_execution

# load messages
messages = load_messages()

# load tools
tools_descriptions = [answer_generic_question_description, solve_math_problem_description, analyze_csv_description, images_management_system_description, answer_hal9_questions_description, analyze_text_file_description, streamlit_generator_description, website_generator_description]
tools_functions = [answer_generic_question, solve_math_problem, analyze_csv, images_management_system, answer_hal9_questions, analyze_text_file, streamlit_generator, website_generator]
tools_descriptions = [python_execution_description, final_response_description, solve_math_problem_description, answer_generic_question_description, analyze_csv_description, images_management_system_description, answer_hal9_questions_description, analyze_text_file_description]
tools_functions = [python_execution, final_response, solve_math_problem, answer_generic_question, analyze_csv, images_management_system, answer_hal9_questions, analyze_text_file]

if len(messages) < 1:
messages = insert_message(messages, "system", "You are Hal9, a helpful and highly capable AI assistant. Your primary responsibility is to analyze user questions and select the most appropriate tool to provide precise, relevant, and actionable responses. Always prioritize using the right tool to ensure efficiency and clarity in your answers.")

messages = insert_message(messages, "system", "You are Hal9, a helpful and highly capable AI assistant. Your primary responsibility is to analyze user questions and select the most appropriate tool to provide precise, relevant, and actionable responses. Always prioritize using the right tool to ensure efficiency and clarity in your answers. If a tool is needed, follow these steps: 1. Identify the best tool for the task. 2. Execute the tool and process its response. 3. If the tool provides a valid result, return it to the user. 4. If the tool fails, do NOT retry with the same tool. Instead, explain the failure and suggest improvements in the prompt or alternative approaches.")
user_input = input()
if is_url(user_input):
filename = user_input.split("/")[-1]
Expand All @@ -34,17 +32,16 @@
else:
user_input = user_input.replace("\f", "\n")
messages = insert_message(messages, "user", user_input)

with ThreadPoolExecutor() as executor:
answer_generic_future = executor.submit(answer_generic_question, user_input)

response = executor.submit(generate_response, "openai", "gpt-4-turbo", messages, tools_descriptions, tool_choice="required", parallel_tool_calls=False)

response_result = response.result()

tool_result = execute_function(response_result, tools_functions)

if tool_result is not None:
insert_tool_message(messages, response_result, tool_result)

save_messages(messages)
steps = 0
max_steps = 6
while steps < max_steps:
response = generate_response("openai", "gpt-4o-mini", messages, tools_descriptions, tool_choice = "required", parallel_tool_calls=False)
tool_result = execute_function(response, tools_functions)
insert_tool_message(messages, response, tool_result)
save_messages(messages, file_path="./.storage/.messages.json")
response_message = response.choices[0].message
tool_calls = getattr(response_message, 'tool_calls', None)
if tool_calls[0].function.name == "final_response":
break
if max_steps == steps:
print("Unable to generate a satisfactory response on time")
4 changes: 0 additions & 4 deletions apps/hal9/tools/calculator.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@

def solve_math_problem(steps_explanation, code_solution):
print("Steps:\n")
print(steps_explanation)
print("\n\nPython Code:\n")
exec(code_solution)
return f"Steps:\n{steps_explanation}\n\n\nPython Code: {code_solution}"

Expand Down
1 change: 0 additions & 1 deletion apps/hal9/tools/csv_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,6 @@ def fix_python_code(csv_path, code):
return f"An error has occurred again -> {last_line} ... Complete traceback: {tb}"

def final_response(final_message):
print(final_message)
return final_message

########################### Descriptions ##########################
Expand Down
19 changes: 2 additions & 17 deletions apps/hal9/tools/generic.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,11 @@
from groq import Groq
from utils import load_messages, insert_message, save_messages
from concurrent.futures import ThreadPoolExecutor

answer_generic_future = None

def answer_generic_question(user_input):
global answer_generic_future
if answer_generic_future is not None:
text_response=answer_generic_future.result()
print(text_response)
return text_response
else:
with ThreadPoolExecutor() as executor:
answer_generic_future = executor.submit(process_request, user_input)
text_response = answer_generic_future.result()
return text_response

def process_request(user_input):
messages = load_messages(file_path="./.storage/.generic_agent_messages.json")
messages = insert_message(messages, "user", user_input)
response = Groq().chat.completions.create(
model="llama3-70b-8192",
model="llama3-8b-8192",
messages=messages,
temperature=0,
seed=1
Expand All @@ -41,7 +26,7 @@ def process_request(user_input):
"properties": {
"user_input": {
"type": "string",
"description": "Take the user input and pass the same string to the function",
"description": "A generic question that requires to be answered",
},
},
"required": ["user_input"],
Expand Down
3 changes: 1 addition & 2 deletions apps/hal9/tools/hal9.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ def answer_hal9_questions(user_input):
temperature = 0,
seed = 1,)

text_response = print(response.choices[0].message.content)
return text_response
return response.choices[0].message.content

answer_hal9_questions_description = {
"type": "function",
Expand Down
9 changes: 4 additions & 5 deletions apps/hal9/tools/image_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ def image_generator(prompt, filename):

generated_description = add_images_descriptions(f".storage/{filename}")

print(generated_description)
return f"The image generated is: {generated_description} \n\n Saved in path: '.storage/{filename}'"

def image_analyzer(image_path, prompt):
Expand All @@ -100,7 +99,7 @@ def image_analyzer(image_path, prompt):
}
],
)
print(response.choices[0].message.content)

return response.choices[0].message.content

def edition_canny_model(image_path, modified_description, filename):
Expand Down Expand Up @@ -129,7 +128,7 @@ def edition_canny_model(image_path, modified_description, filename):
shutil.copy(filename, f".storage/{filename}")

generated_description = add_images_descriptions(f".storage/{filename}")
print(generated_description)

return f"The image generated is: {generated_description} \n\n Saved in path: '.storage/{filename}'"

def edition_depth_model(image_path, modified_description, filename):
Expand Down Expand Up @@ -157,7 +156,7 @@ def edition_depth_model(image_path, modified_description, filename):
shutil.copy(filename, f".storage/{filename}")

generated_description = add_images_descriptions(f".storage/{filename}")
print(generated_description)

return f"The image generated is: {generated_description} \n\n Saved in path: '.storage/{filename}'"

def generate_image_variation(image_path, filename):
Expand Down Expand Up @@ -187,7 +186,7 @@ def generate_image_variation(image_path, filename):
shutil.copy(filename, f".storage/{filename}")

generated_description = add_images_descriptions(f".storage/{filename}")
print(generated_description)

return f"The image generated is: {generated_description} \n\n Saved in path: '.storage/{filename}'"

########################### Descriptions ##########################
Expand Down
23 changes: 23 additions & 0 deletions apps/hal9/tools/other_tools.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
def final_response(final_message):
print(final_message)
return final_message

final_response_description = {
"type": "function",
"function": {
"name": "final_response",
"description": "This function is called when all necessary steeps has been completed through the tools, and all the requirements or the information requested by the user has been collected. It finalizes the process and delivers the results in a clear and concise message.",
"strict": True,
"parameters": {
"type": "object",
"properties": {
"final_message": {
"type": "string",
"description": "A clear and concise message that in simple terms mentions all the tools called to obtain the information neccesary. It explains how the information was gathered and whats the final insight.",
},
},
"required": ["final_message"],
"additionalProperties": False,
},
}
}
92 changes: 92 additions & 0 deletions apps/hal9/tools/python_execution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
from utils import generate_response, insert_message, extract_code_block
import traceback
from clients import openai_client
import time
import sys
import io

def fix_code(chat_input, error, complete_traceback, python_code):
stream = openai_client.chat.completions.create(
model = "gpt-4-turbo",
messages = [
{"role": "user", "content":
f"""The following Python code needs to be fixed. It should fulfill this user request: '{chat_input}', return the fixed code as fenced code block with triple backticks (```) as ```python```
### Error encountered:
{error}
### Code that needs fixing:
{python_code}
### Complete error traceback:
{complete_traceback}
"""
},]
)
return extract_code_block(stream.choices[0].message.content, "python")

def debug_code(python_code):
try:
context = {}
timestamp = int(time.time())
with open(f"script_{timestamp}.txt", "w") as file:
file.write(python_code)
stdout_backup = sys.stdout
sys.stdout = io.StringIO()
exec(python_code, context)
output = sys.stdout.getvalue()
sys.stdout = stdout_backup
return f"Code Works properly: result of the execution: {output}", "", ""
except Exception as e:
tb = traceback.format_exc()
relevant_error_info = tb.splitlines()
last_line = relevant_error_info[-1]
complete_traceback="\n".join(relevant_error_info)
return "App fails to run", last_line, complete_traceback

def python_execution(prompt):
# load messages
messages = []

if len(messages) < 1:
messages = insert_message(messages, "system", f"This Python generation system creates scripts from user prompts, including necessary imports; always include a print message indicating the process finished or the output. If a file is generated, it must be stored in './.storage/'. Return the code as a fenced block using triple backticks (```python```).")
messages = insert_message(messages, "user", f"Generates an app that fullfills this user request -> {prompt}")
model_response = generate_response("openai", "gpt-4-turbo", messages)
response_content = model_response.choices[0].message.content
generated_code = extract_code_block(response_content, "python")
# Debug and fix the code if needed
max_tries = 3
tries = 0
while tries < max_tries:
result, error, complete_traceback = debug_code(generated_code)
if result.startswith("Code Works properly"):
messages = insert_message(messages, "assistant", generated_code)
return f"{result} ... This is the final code generated -> {generated_code}"
else:
generated_code = fix_code(prompt, error, complete_traceback, generated_code)
tries += 1

return f"Unable to generate an app that fulfills your request without errors. -> this was the final code obtained {generated_code} and the error found -> {error}"

python_execution_description = {
"type": "function",
"function": {
"name": "python_execution",
"description": "Executes and debugs Python scripts to solve problems that cannot be addressed with other tools. It can run arbitrary Python code to generate files or perform complex calculations.",
"strict": True,
"parameters": {
"type": "object",
"properties": {
"prompt": {
"type": "string",
"description": "A detailed natural language description of the desired python code solution",
}
},
"required": ["prompt"],
"additionalProperties": False,
},
}
}
2 changes: 0 additions & 2 deletions apps/hal9/tools/streamlit.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,11 @@ def streamlit_generator(prompt):
save_python_code(streamlit_code)
messages = insert_message(messages, "assistant", streamlit_code)
save_messages(messages, file_path="./.storage/.streamlit_messages.json")
print(result)
return result
else:
streamlit_code = fix_code(prompt, error, complete_traceback, streamlit_code)
tries += 1

print("Unable to generate an app that fulfills your request without errors.")
return "Unable to generate an app that fulfills your request without errors."

streamlit_generator_description = {
Expand Down
1 change: 0 additions & 1 deletion apps/hal9/tools/text_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,6 @@ def random_pick_chunks(num_chunks, file_to_filter=None):
return selected_chunks.to_dict(orient='records')

def final_response(final_message):
print(final_message)
return final_message

########################### Descriptions ##########################
Expand Down
1 change: 0 additions & 1 deletion apps/hal9/tools/website.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ def website_generator(prompt):
messages.append({"role": "user", "content": "briefly describe what was accomplished"})
completion = client.chat.completions.create(model = "gpt-4", messages = messages)
summary = h9.complete(completion, messages, show = False)
print(summary)
return summary

website_generator_description = {
Expand Down

0 comments on commit bfc330e

Please sign in to comment.