Last active
October 4, 2023 07:38
-
-
Save peterroelants/f3508b85b5574576b5f85cd90c721a9d to your computer and use it in GitHub Desktop.
Simple example of OpenAI Function Calling in ReAct Loop
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Simple example of OpenAI Function Calling in ReAct Loop. | |
Functions are described in JSON with each parameter the function accepts described as a JSON Schema object. | |
More info: | |
- OpenAI Function Calling | |
- https://openai.com/blog/function-calling-and-other-api-updates | |
- https://platform.openai.com/docs/guides/gpt/function-calling | |
- https://platform.openai.com/docs/api-reference/chat/create#chat/create-functions | |
- JSON Schema | |
- https://json-schema.org/ | |
- https://json-schema.org/specification-links#2020-12 | |
- ReAct | |
- https://arxiv.org/abs/2210.03629 | |
- https://peterroelants.github.io/posts/react-repl-agent/ | |
""" | |
import inspect | |
import json | |
import openai | |
# Functions ######################################################## | |
class StopException(Exception): | |
""" | |
Stop Execution (Task is Finished) | |
""" | |
... | |
def finish(answer): | |
"""Answer the user's question, and finish the conversation.""" | |
raise StopException(answer) | |
def get_current_location(): | |
"""Get the current location of the user""" | |
# Mocked for the sake of the example | |
return json.dumps( | |
{ | |
"latitude": 50.9326, | |
"longitude": 5.34260, | |
} | |
) | |
def get_current_weather(latitude, longitude): | |
"""Get the current weather in a given location""" | |
# Mocked for the sake of the example | |
weather_info = { | |
"location": (latitude, longitude), | |
"temperature": "72", | |
"unit": "fahrenheit", | |
"forecast": "sunny", | |
} | |
return json.dumps(weather_info) | |
def calculate(formula): | |
"""Calculate the result of a given formula""" | |
return str(eval(formula)) | |
name_to_function_map = { | |
get_current_location.__name__: get_current_location, | |
get_current_weather.__name__: get_current_weather, | |
calculate.__name__: calculate, | |
finish.__name__: finish, | |
} | |
# The parameters the functions accepts, described as a JSON Schema object. | |
functions = [ | |
{ | |
"name": get_current_location.__name__, | |
"description": inspect.getdoc(get_current_location).strip(), | |
"parameters": { | |
"type": "object", | |
"properties": {}, | |
"required": [], | |
}, | |
}, | |
{ | |
"name": get_current_weather.__name__, | |
"description": inspect.getdoc(get_current_weather).strip(), | |
"parameters": { | |
"type": "object", | |
"properties": { | |
"latitude": {"type": "number"}, | |
"longitude": {"type": "number"}, | |
}, | |
"required": ["latitude", "longitude"], | |
}, | |
}, | |
{ | |
"name": calculate.__name__, | |
"description": inspect.getdoc(calculate).strip(), | |
"parameters": { | |
"type": "object", | |
"properties": { | |
"formula": { | |
"type": "string", | |
"description": "Formula to compute the result of, in Python syntax.", | |
}, | |
}, | |
"required": ["formula"], | |
}, | |
}, | |
{ | |
"name": finish.__name__, | |
"description": inspect.getdoc(finish).strip(), | |
"parameters": { | |
"type": "object", | |
"properties": { | |
"answer": { | |
"type": "string", | |
"description": "Text to answer the user with.", | |
}, | |
}, | |
"required": ["answer"], | |
}, | |
}, | |
] | |
# Function Calling in loop ######################################### | |
# Initial "chat" messages | |
messages = [ | |
{ | |
"role": "system", | |
"content": "You are a helpful assistant that can answer multi-step questions by sequentially calling functions. Follow a pattern of THOUGHT (reason step-by-step about which function to call next), ACTION (call a function to get closer to the answer), OBSERVATION (output of the function).", | |
}, | |
{ | |
"role": "user", | |
"content": "What's the current weather for my location? Give me the temperature in degrees Celsius.\n\nReason step by step which actions to take to get to the answer.", | |
}, | |
] | |
# Run in loop | |
max_iterations = 20 | |
for i in range(max_iterations): | |
print(f"\n\n# Iteration {i}:") | |
# Call OpenAI's API to get the next message | |
response = openai.ChatCompletion.create( | |
model="gpt-3.5-turbo", | |
messages=messages, | |
functions=functions, | |
function_call="auto", | |
) | |
response_message = response["choices"][0]["message"] | |
# Extend conversation with assistant's reply | |
messages.append(response_message.to_dict()) | |
# Check if GPT wanted to call a function | |
if response_message.get("function_call"): | |
function_call_message = response_message["function_call"] | |
print(f"function_call_message={json.dumps(function_call_message, indent=2)}") | |
# Call the function | |
function_name = function_call_message["name"] | |
function_to_call = name_to_function_map[function_name] | |
function_args = function_call_message["arguments"] | |
try: | |
function_args_dict = json.loads(function_args) | |
except json.JSONDecodeError as exc: | |
# JSON decoding failed | |
print(f"Error decoding function arguments: {exc}") | |
messages.append( | |
{ | |
"role": "function", | |
"name": function_name, | |
"content": f"Error decoding function arguments {function_args!r}! Error: {exc}", | |
} | |
) | |
continue | |
try: | |
function_response = function_to_call(**function_args_dict) | |
except StopException as exc: | |
# Agent wants to stop the conversation (Expected) | |
print(f"Stopping conversation: {exc}") | |
break | |
except Exception as exc: | |
# Unexpected error calling function | |
print(f"Error calling function {function_name}: {exc}") | |
messages.append( | |
{ | |
"role": "function", | |
"name": function_name, | |
"content": f"Error calling function {function_name}: {exc}!", | |
} | |
) | |
continue | |
print(f"{function_response=}") | |
# Extend conversation with function response | |
messages.append( | |
{ | |
"role": "function", | |
"name": function_name, | |
"content": function_response, | |
} | |
) | |
print(f"messages: {json.dumps(messages, indent=2)}") | |
# [ | |
# { | |
# "role": "system", | |
# "content": "You are a helpful assistant that can answer multi-step questions by sequentially calling functions. Follow a pattern of THOUGHT (reason step-by-step about which function to call next), ACTION (call a function to get closer to the answer), OBSERVATION (output of the function)." | |
# }, | |
# { | |
# "role": "user", | |
# "content": "What's the current weather for my location? Give me the temperature in degrees Celsius.\n\nReason step by step which actions to take to get to the answer." | |
# }, | |
# { | |
# "role": "assistant", | |
# "content": "To get the current weather for the user's location, we need to follow these steps:\n\n1. Get the user's current location.\n2. Retrieve the latitude and longitude coordinates from the user's location.\n3. Use the coordinates to get the current weather.\n4. Extract the temperature from the weather data.\n5. Convert the temperature to degrees Celsius.\n\nNow, let's take these steps one by one and call the appropriate functions to get the answer.", | |
# "function_call": { | |
# "name": "get_current_location", | |
# "arguments": "{}" | |
# } | |
# }, | |
# { | |
# "role": "function", | |
# "name": "get_current_location", | |
# "content": "{\"latitude\": 50.9326, \"longitude\": 5.3426}" | |
# }, | |
# { | |
# "role": "assistant", | |
# "content": null, | |
# "function_call": { | |
# "name": "get_current_weather", | |
# "arguments": "{\n \"latitude\": 50.9326,\n \"longitude\": 5.3426\n}" | |
# } | |
# }, | |
# { | |
# "role": "function", | |
# "name": "get_current_weather", | |
# "content": "{\"location\": [50.9326, 5.3426], \"temperature\": \"72\", \"unit\": \"fahrenheit\", \"forecast\": \"sunny\"}" | |
# }, | |
# { | |
# "role": "assistant", | |
# "content": "The current weather for your location is sunny with a temperature of 72 degrees Fahrenheit. \n\nTo convert the temperature to Celsius, we can use the formula: \n\nCelsius = (Fahrenheit - 32) * 5/9\n\nLet's calculate it.", | |
# "function_call": { | |
# "name": "calculate", | |
# "arguments": "{\n \"formula\": \"(72 - 32) * 5/9\"\n}" | |
# } | |
# }, | |
# { | |
# "role": "function", | |
# "name": "calculate", | |
# "content": "22.22222222222222" | |
# }, | |
# { | |
# "role": "assistant", | |
# "content": "The current temperature in your location is approximately 22.22 degrees Celsius." | |
# }, | |
# { | |
# "role": "assistant", | |
# "content": null, | |
# "function_call": { | |
# "name": "finish", | |
# "arguments": "{\n \"answer\": \"The current weather for your location is sunny with a temperature of 22.22 degrees Celsius.\"\n}" | |
# } | |
# } | |
# ] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment