I am currently exploring the capabilities of OpenAI tools to determine if they can autonomously identify and arrange the necessary functions in the correct order to address a user’s request. My test involves a code with four distinct functions. However, I’m facing a challenge where the final function in the sequence does not seem to receive the complete list of friends as its parameter.
I’m curious if anyone might have insights into this issue. Additionally, I’m uncertain about the proper way to conclude the sequence of tool usage. For this, I’ve been utilizing the completion API to signal the end of a function call. I am thinking of the use case where the tools can be 100’s of function and if function calling should be able to handle or not. Appreciate if anyone has any insights
https://gitlab.com/-/snippets/3641029/raw/main/openai_function_calling.py
import os
import openai
from openai import OpenAI
import pdb
from datetime import date
import random
import traceback
openai.api_key = os.getenv('OPENAI_API_KEY')
system_prompt = """You are a function router AI. You will build a knowledge graph of functions and their inputs and outputs. You will parse input query and
find the correct sequence of functions to call to return the final correct result. Since the functions may be executed in a sequential manner, you do not need to ask for confirmation on proceeding. Assume user intends to execute all the functions needed to answer the query. """
tools = [
{
"type": "function",
"function": {
"name": "find_todays_date",
"description": "Find today's date",
"parameters": {
"type": "object",
"properties": {},
"required": []
},
},
},
{
"type": "function",
"function": {
"name": "find_list_of_friends",
"description": "Find a list of friends",
"parameters": {
"type": "object",
"properties": {},
"required": []
},
},
},
{
"type": "function",
"function": {
"name": "find_birthdays_of_friends",
"description": "Find birthdays of friends given a list of friends",
"parameters": {
"type": "object",
"properties": {
"friends_list": {
"type": "array",
"items": {
"type": "string"
},
"description": "A list of friends"
},
},
"required": ["friends_list"]
},
},
},
{
"type": "function",
"function": {
"name": "send_birthday_greetings",
"description": "Send email greetings if today is a friend's birthday",
"parameters": {
"type": "object",
"properties": {
"birthdays_list": {
"type": "array",
"items": {
"type": "object",
"properties": {
"friend": {"type": "string", "description": "Name of the friend"},
"date_of_birth": {"type": "string", "description": "Date of birth of the friend"}
},
"description": "A list of friends' birthdays"
}
},
"today": {
"type": "string",
"description": "The current date in a specific format, e.g., '2024-01-21'"
}
},
"required": ["birthdays_list", "today"]
}
}
}
]
# Dummy function to simulate finding today's date
def find_todays_date():
# Returns today's date
return str(date.today())
# Dummy function to simulate finding a list of friends
def find_list_of_friends():
# Fake data: returns a list of mock friend names
return ["Alice", "Bob", "Charlie", "Diana", "Eve"]
# Dummy function to simulate finding birthdays of friends
def find_birthdays_of_friends(friends_list):
# Fake data: assigns a random birthday to each friend
return {friend: f"199{random.randint(0, 9)}-0{random.randint(1, 9)}-{random.randint(10, 28)}" for friend in friends_list}
# Dummy function to simulate sending birthday greetings
def send_birthday_greetings(birthdays_list, today):
# Fake operation: returns a success message for each friend
for birthday in birthdays_list:
friend = birthday["friend"]
date_of_birth = birthday["date_of_birth"]
print(f"Sent birthday greeting to {friend} born on {date_of_birth}")
return "Greetings sent"
def get_response(messages):
client = OpenAI()
response = client.chat.completions.create(
model="gpt-3.5-turbo-1106",
messages=messages,
tools=tools,
tool_choice="auto", # auto is default, but we'll be explicit
)
return response
def run_tools(tool_calls, messages):
fnc_results = []
for t in tool_calls:
fnc = t.function.name
args = t.function.arguments
args = eval(args)
print(f"Executing {fnc}..")
fnc_result = eval(f"{fnc}(**args)")
msg = f"Function {fnc} with arguments {args} returned {fnc_result}."
print(msg)
messages.append({"role": "tool", "tool_call_id": t.id, "name": fnc, "content": str(fnc_result)})
return messages
def check_is_complete(query, response_content):
try:
# check if done
if response_content == None:
return False
client = OpenAI()
messages = []
system_prompt = "You check the status of last message. You only check if the message is indicative of more steps needed or not. If complete respond yes. Otherwise, respond no."
message = f"""Last message received is ####\n {response_content} ###\n.
Typically when complete, message would indicate All steps have been successfully completed
Check if the last message is implying more steps are needed or if all the steps are complete. please respond with 'yes' if more steps are needed. Otherwise, please respond with 'no'."""
messages.append({"role": "system", "content": system_prompt,})
messages.append({"role": "user", "content": message,})
response = client.chat.completions.create(
model="gpt-3.5-turbo-1106",
messages=messages
)
print(f"\n\n####check_is_complete: last_message is {response_content}. response was {response}\n\n")
is_done = 'yes' in response.choices[0].message.content.lower()
print(f"Is done? {is_done} from {response_content}.")
return is_done
except Exception as e:
print(e)
print(traceback.format_exc())
pdb.set_trace()
return False
def check_is_complete(query, response_content):
try:
# Verification step
if response_content is None:
return False
client = OpenAI()
messages = []
system_prompt = "As an AI assistant, your task is to determine if a message indicates completion of all necessary steps or if additional actions are required. Reply 'yes' if more steps are needed based on the message, or 'no' if it indicates that everything is complete. Only respond yes or no text"
message = f"""I've received the following message: '{response_content}'.
Generally, a message signaling completion would explicitly state that all required steps have been successfully finished.
Please review whether this message suggests that there
are additional steps to be taken or if it signifies the completion of tasks.
Note that there might be other messages detailing what has been completed
regarding the original query '{query}'. However,
my focus is solely on determining the completion status of the steps.
Respond with 'yes' if additional steps are necessary, or 'no' if
everything is complete. Only respond yes or no."""
messages.append({"role": "system", "content": system_prompt})
messages.append({"role": "user", "content": message})
response = client.chat.completions.create(
model="gpt-3.5-turbo-1106",
messages=messages
)
#print(f"\n\n####check_is_complete: last_message is {response_content}. response was {response}\n\n")
do_more = 'yes' in response.choices[0].message.content.lower()
#print(f"Do more steps need to be taken? {do_more} based on '{response_content}'.")
return not do_more
except Exception as e:
print(e)
return False
query = "Find a list of friends and send birthday greetings if today is a friend's birthday"
chain_message = "Find a list of friends and send birthday greetings if today is a friend's birthday"
messages = []
messages.append({"role": "system", "content": system_prompt,})
messages.append({"role": "user", "content": chain_message,})
while True:
try:
response = get_response(messages)
response_message = response.choices[0].message
response_content = response_message.content
tool_calls = response_message.tool_calls
#print(response_message)
#print(response_content)
is_complete = check_is_complete(query, response_content)
if is_complete and tool_calls == None:
break
if not tool_calls:
continue
assistant_message = {"role": "assistant", 'tool_calls': tool_calls}
messages.append(assistant_message)
messages = run_tools(tool_calls, messages)
next_question = "What step should I take next?"
messages.append({"role": "user", 'content': next_question})
except Exception as e:
print(e)
print(traceback.format_exc())
pdb.set_trace()
break