""" 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}" # } # } # ]