Files
wehub-resource-sync bbfc60cd69
Publish BFCL to PyPI / build_and_publish (push) Has been cancelled
Update API Zoo Data / send-updates (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:37:27 +08:00

312 lines
13 KiB
Python

import requests
import os
from openai import OpenAI, APIError
from collections import defaultdict
from exec_engine.credentials.credentials_utils import *
from exec_engine.utils import SQL_Type, Filesystem_Type, RESTful_Type
import re
python_pattern = r"```python\n(.*?)\n```"
sql_pattern = r"```sql\n(.*?)\n```"
bash_pattern = r"```(?:bash|shell|sh)\n(.*?)\n```"
INSTRUCTION_TEXT = """
You are an assistant that outputs executable Python code that perform what the user requests.
It is important that you only return one and only one code block with all the necessary imports inside ```python and nothing else.
The code block should print the output(s) when appropriate.
If the action can't be successfully completed, throw an exception
This is what the user requests: {request}\n
"""
REVERSE_INSTRUCTION_TEXT = """
Given an action and a Python code block that performs that action from the user,
you are an assistant that outputs executable Python code that perform the REVERSE (can make more/new API calls if needed) of what's been done.
It is important that the REVERSE code only revert the changes if any and nothing else, and that you only return
one and only one code block with all the necessary imports inside ```python and nothing else.
The code block should print the output(s) when appropriate.
If the action can't be successfully completed, throw an exception
This is the action: {prompt}
This is the action code block: {forward_call}
If no revert action exists, return a python code with a print statement explaining why so.
"""
FUNCTION_IN_CONTEXT_TEXT = """
You are an assistant that outputs executable Python code according to a given list of python functions that perform what the user requests.
It is important that you only return one and only one code block with all the necessary imports inside ```python and nothing else.
The code block should print the output(s) when appropriate.
If the action can't be successfully completed, throw an exception
Don't change the available functions listed below except for feeding parameters.
Only use the functions defined below, don't add new functions.
Here is a list of python functions available:\n
{functions}\n
This is what the user requests: {request}\n
"""
REVERSE_FUNCTION_IN_CONTEXT_TEXT = """
Given a list of available functions, an action and a Python code block that performs that action from the user,
you are an assistant that outputs executable Python code using the available functions provided that perform the REVERSE (can make more/new API calls if needed) of what's been done.
It is important that the REVERSE code only revert the changes if any and nothing else, and that you only return
one and only one code block with all the necessary imports inside ```python and nothing else.
The code block should print the output(s) when appropriate.
Don't change the available functions listed below except for feeding parameters.
Only use the functions defined below, don't add new functions.
If the action can't be successfully completed, throw an exception
Here is a list of python functions available:\n
{functions}\n
This is the action: {prompt}
This is the action code block: {forward_call}
If no revert action exists, return a python code with a print statement explaining why so.
"""
FUNCTION_CALLING_NATIVE_TEXT = """
{request}
"""
REVERSE_FUNCTION_CALLING_NATIVE_TEXT = """
Given an action and a Python code block that performs that action from the user,
perform the REVERSE of what's been done.
This is the action: {prompt}
This is the action code block: {forward_call}
"""
def read_available_functions():
FUNCTION_DIR = "./function/"
avail_functions = {}
functions = os.listdir(FUNCTION_DIR)
for function in functions:
if not function.endswith(".py"):
continue
with open(FUNCTION_DIR + function, "r") as file:
avail_functions[function.replace(".py", "")] = file.read()
return avail_functions
def convert_to_function_call(function_call_list):
if type(function_call_list) == dict:
function_call_list = [function_call_list]
execution_list = []
for function_call in function_call_list:
for key, value in function_call.items():
execution_list.append(
f"{key}({','.join([f'{k}={repr(v)}' for k,v in json.loads(value).items()])})"
)
return execution_list
def generate_command(content, credentials = None, api_type=RESTful_Type, generate_mode='default', openai_model="gpt-4-turbo-preview"):
client = OpenAI()
# defaults to getting the key using os.environ.get("OPENAI_API_KEY")
# if you saved the key under a different environment variable name, you can do something like:
# client = OpenAI(
# api_key=os.environ.get("CUSTOM_ENV_NAME"),
# )
if api_type == SQL_Type:
prompt = content
pattern = sql_pattern
elif api_type == Filesystem_Type:
prompt = content
pattern = bash_pattern
elif api_type == RESTful_Type or True:
if generate_mode == 'default':
prompt = INSTRUCTION_TEXT.format(request=content)
elif generate_mode == 'function_in_context':
avail_functions = read_available_functions()
prompt_functions = ""
for idx, function in enumerate(list(avail_functions.values())):
prompt_functions += "<<function" + str(idx) + ">>: " + str(function) + "\n"
prompt = FUNCTION_IN_CONTEXT_TEXT.format(functions=prompt_functions, request=content)
elif generate_mode == 'function_calling_native':
prompt = FUNCTION_CALLING_NATIVE_TEXT.format(request=content)
else:
raise Exception("Error: {} is not a supported argument for generate_mode, please select again from 'default', 'function_in_context', 'function_calling_native'"
.format(generate_mode))
if credentials:
prompt += generate_credentials_prompt_info(credentials)
else:
prompt += "Equally importantly, try to do so without requiring any API key. If and only if one is really needed, give the code block assuming those keys are provided."
pattern = python_pattern
if generate_mode == 'function_calling_native':
function_calls = json.load(open('./function/function.json'))
response = client.chat.completions.create(
model=openai_model,
messages=[
{
"role": "user",
"content": prompt
}
],
tools=function_calls,
tool_choice=None,
)
if response.choices[0].message.tool_calls == None:
raise Exception("Unable able to retreive the relevant command to perform the action from Function Calling.\n" +
"Try again with a different prompt or generate_mode setting")
output = [
{func_call.function.name: func_call.function.arguments}
for func_call in response.choices[0].message.tool_calls
]
assert len(output) == 1
output = output[0]
python_code = open(f"./function/{list(output.keys())[0]}.py", 'r').read()
output = convert_to_function_call(output)[0]
output = "```python\n" + python_code + "\n" + output + "\n```"
else:
response = client.chat.completions.create(
model=openai_model,
messages=[
{
"role": "user",
"content": prompt
}
]
)
output = response.choices[0].message.content
#print(output) #Intentionally left here for future debugging purpose
matches = re.search(pattern, output, re.DOTALL)
if matches:
code = matches.group(1)
return code
def generate_reverse_command(forward_call, prompt, credentials=None, api_type=RESTful_Type, generate_mode='default', openai_model="gpt-4-turbo-preview"):
client = OpenAI()
if api_type == RESTful_Type:
if generate_mode == 'default':
reverse_prompt = REVERSE_INSTRUCTION_TEXT.format(prompt=prompt, forward_call=forward_call)
elif generate_mode == 'function_in_context':
avail_functions = read_available_functions()
prompt_functions = ""
for idx, function in enumerate(list(avail_functions.values())):
prompt_functions += "<<function" + str(idx) + ">>: " + str(function) + "\n"
reverse_prompt = REVERSE_FUNCTION_IN_CONTEXT_TEXT.format(prompt=prompt, forward_call=forward_call, functions=prompt_functions)
elif generate_mode == 'function_calling_native':
reverse_prompt = REVERSE_FUNCTION_CALLING_NATIVE_TEXT.format(prompt=prompt, forward_call=forward_call)
else:
raise Exception("Error: {} is not a supported argument for generate_mode, please select again from 'default', 'function_in_context', 'function_calling_native'"
.format(generate_mode))
if credentials:
reverse_prompt += generate_credentials_prompt_info(credentials)
else:
reverse_prompt += "Equally importantly, try to do so without requiring any API key. If and only if one is really needed, give the code block assuming those keys are provided."
else:
raise Exception("generate_reverse_command not supported for execution type other than REST")
if generate_mode == 'function_calling_native':
function_calls = json.load(open('./function/function.json'))
response = client.chat.completions.create(
model=openai_model,
messages=[
{
"role": "user",
"content": reverse_prompt
}
],
tools=function_calls,
tool_choice=None,
)
output = [
{func_call.function.name: func_call.function.arguments}
for func_call in response.choices[0].message.tool_calls
]
assert len(output) == 1
output = output[0]
python_code = open(f"./function/{list(output.keys())[0]}.py", 'r').read()
output = convert_to_function_call(output)[0]
output = "```python\n" + python_code + "\n" + output + "\n```"
else:
response = client.chat.completions.create(
model=openai_model,
temperature=0.3,
messages=[
{
"role": "user",
"content": reverse_prompt
}
]
)
output = response.choices[0].message.content
#print(output) #Intentionally left here for future debugging purpose
matches = re.search(python_pattern, output, re.DOTALL)
if matches:
code = matches.group(1)
return code
def prompt_execute(engine, prompt, services=None, creds=None, max_attempt=3, model="gpt-4-turbo-preview"):
ret = defaultdict(list)
for _ in range(max_attempt):
forward_call, backward_call = engine.gen_api_pair(prompt, api_type=RESTful_Type, credentials=creds, model=model)
response = engine.api_executor.execute_api_call(forward_call, services)
if response and response['output']:
ret['output'].append(response['output'])
return ret, forward_call, backward_call
elif response and response['debug']:
ret['debug'].append(response['debug'])
engine._add_api_reverse_to_queue(RESTful_Type, (forward_call, backward_call))
return ret, forward_call, None
def generate_credentials_prompt_info(credentials):
#credential comes in a [service_name, value, cred_type] format
token_in_path = []
raw_key = []
try:
for service_name, value, cred_type in credentials:
if cred_type == "path":
token_in_path.append([service_name, value])
elif cred_type == "raw":
raw_key.append([service_name, value])
except:
raise Exception("Error: credentials have to be passed in as a list of [service_name, value, cred_type] pairs")
if token_in_path != []:
cred_paths = {}
for service_name, value in token_in_path:
prefix = "./credentials/" + service_name + "/"
cred_paths[service_name] = [prefix + file_name for file_name in os.listdir(value)]
return "The credentials (such as API keys) are stored in the following paths: {keys_list}. Open the file(s) to access them".format(keys_list=cred_paths)
if raw_key != []:
return "Additionally, these api keys are available for the following services {key_list}".format(
key_list=" ,".join("{name}={cred}".format(name=name, cred=cred) for name,cred in raw_key))