We are going to create an AI agent which can perform timezone conversion with tools / function calling. Only openai
library is used.
Full source code will be given at the end.
Set up
We only need to install openai
library.
pip install openai
Define tools
We will define 2 tools. One is to get today's date and the other is to convert the timezone.
We also define tool_map
which stores the details about the tools. The key of the map is the function name. prompt
is the details we will send to LLM and function
is the function we will call.
If your tools perform sensitive operations, you should verify or sanitize the parameters first.
def get_today_date(timezone):
return datetime.datetime.now(ZoneInfo(timezone)).strftime("%Y-%m-%d")
def convert_timezone(source_timezone, target_timezone, year, month, day, hour, minute):
dt = datetime.datetime(year, month, day, hour, minute, tzinfo=ZoneInfo(source_timezone))
return dt.astimezone(ZoneInfo(target_timezone))
tool_map = {
"get_today_date": {
"prompt": """get_today_date(timezone)
e.g. get_today_date("Asia/Tokyo")
Description: Get the date of today.
""",
"function": get_today_date,
},
"convert_timezone": {
"prompt": """convert_timezone(source_timezone, target_timezone, year, month, day, hour, minute)
e.g. convert_timezone("Asia/Tokyo", "Europe/London" 2025, 2, 15, 13, 0)
Description: Convert time from source timezone to target timezone
""",
"function": convert_timezone
}
}
Define system prompt
We apply "ReAct Prompting" to enable act and reasoning abilities. This also allows us to use tools.
In order to make LLM stop generating after proposing an action, we ask it to output "PAUSE" text after an action.
system_prompt = f"""Answer the following questions as best you can. You have access to the following tools:
{"\n".join([tool["prompt"] for tool in tool_map.values()])}
If you need to use the tool, you MUST return PAUSE!!!! This is very IMPORTANT.
Use the following format:
Question: the input question you must answer
Thought: you should always think about what to do
Action: the action to take, should be one of [{", ".join(tool_map.keys())}]. This ONLY includes the name of the tool.
Action Input: the input to the action. This ONLY includes the parameters of the tool. Example: Action Input: a, "b", c
PAUSE
Observation: the result of the action
Thought: I now know the final answer
Final Answer: the final answer to the original input question
Begin!"""
Pseudocode of AI agent
Before we see the actual code of AI agent, we check the pseudocode first.
We have a loop to keep sending messages to LLM and performing actions.
messages = [system_prompt, question_prompt]
completion = chat(llm, messages)
add completion to messages with role 'assistant'
while 'Final Answer' is not in completion and maximum round is not reached:
If 'Action' is not found in completion:
continue
action = parse_action(completion)
If 'Action Input' is not in completion:
continue
action_input = parse_action_input(completion)
result = action(acion_input)
add result to messages with role 'assistant'
completion = chat(llm, messages)
add completion to messages with role 'assistant'
chat
function
chat
function is straight forward. It only creates chat completion with the messages we have.
Stop word "PAUSE" is used to make LLM stop generation after proposing an action.
def chat(client, messages) -> str:
completion = client.chat.completions.create(messages=messages, model="gpt-4o", stop="PAUSE")
completion_content = completion.choices[0].message.content
print("Chat completion:")
print(completion_content)
return completion_content
parse_action
function
We use regular expression to extract tool function name. Tool function will be returned. We also make sure tool function is defined in tool_map
for security reason.
You can also change the system prompt to ask LLM returning JSON to ease parsing.
def parse_action(completion_content: str):
action_pattern = r"Action: ([^\n]*)"
match = re.search(action_pattern, completion_content)
if not match:
return None
tool_name = match.group(1)
if tool_name not in tool_map:
raise ValueError(f"Tool '{tool_name}' does not exist")
return tool_map[tool_name]
parse_action_input
function
As same as parse_action
, we use regular expression to extract parameters of tool.
def parse_action_input(completion_content: str):
action_input_pattern = r"Action Input: ([^\n]*)"
match = re.search(action_input_pattern, completion_content)
if not match:
return None
args_string = match.group(1)
return eval(f"({args_string},)")
AI agent main logic
Here is the implementaiton of the above pseudocode.
def main():
client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
question = "Convert 13:49 today in London time to Japan time. Please consider daylight saving."
print(f"Question: {question}")
messages = [{"role": "system", "content": system_prompt}, {"role": "user", "content": f"Question: {question}"}]
completion_content = chat(client, messages)
messages.append({"role": "assistant", "content": completion_content})
current_round = 0
max_round = 5
while "Answer" not in completion_content and current_round < max_round:
current_round += 1
action = parse_action(completion_content)
if action is None:
continue
action_input = parse_action_input(completion_content)
if action_input is None:
continue
action_result = action["function"](*action_input)
print(f"Action result: {action_result}")
messages.append({"role": "assistant", "content": f"Observation: {action_result}"})
completion_content = chat(client, messages)
messages.append({"role": "assistant", "content": completion_content})
if __name__ == '__main__':
main()
Sample result
Question: Convert 13:49 today in London time to Japan time. Please consider daylight saving.
Chat completion:
Thought: To accurately convert 13:49 London time to Japan time, it's important to consider the current date and whether daylight saving time is in effect. I'll first fetch the current date in the "Europe/London" timezone to understand if daylight saving time needs to be taken into account.
Action: get_today_date
Action Input: "Europe/London"
Action result: 2025-02-23
Chat completion:
Thought: Today is February 23, 2025. Daylight saving time in London typically starts on the last Sunday in March and ends on the last Sunday in October. Therefore, currently, London is on Greenwich Mean Time (GMT, UTC+0). Japan does not observe daylight saving time and is always on Japan Standard Time (JST, UTC+9). I will now convert 13:49 London time to Japan time for today.
Action: convert_timezone
Action Input: "Europe/London", "Asia/Tokyo", 2025, 2, 23, 13, 49
Action result: 2025-02-23 22:49:00+09:00
Chat completion:
Thought: I have successfully converted 13:49 London time to Japan time. The converted time is 22:49 on February 23, 2025.
Final Answer: 13:49 in London on February 23, 2025, is 22:49 in Japan time.
Full source code
import datetime
import os
import re
from zoneinfo import ZoneInfo
from openai import OpenAI
def get_today_date(timezone):
return datetime.datetime.now(ZoneInfo(timezone)).strftime("%Y-%m-%d")
def convert_timezone(source_timezone, target_timezone, year, month, day, hour, minute):
dt = datetime.datetime(year, month, day, hour, minute, tzinfo=ZoneInfo(source_timezone))
return dt.astimezone(ZoneInfo(target_timezone))
tool_map = {
"get_today_date": {
"prompt": """get_today_date(timezone)
e.g. get_today_date("Asia/Tokyo")
Description: Get the date of today.
""",
"function": get_today_date,
},
"convert_timezone": {
"prompt": """convert_timezone(source_timezone, target_timezone, year, month, day, hour, minute)
e.g. convert_timezone("Asia/Tokyo", "Europe/London" 2025, 2, 15, 13, 0)
Description: Convert time from source timezone to target timezone
""",
"function": convert_timezone
}
}
system_prompt = f"""Answer the following questions as best you can. You have access to the following tools:
{"\n".join([tool["prompt"] for tool in tool_map.values()])}
If you need to use the tool, you MUST return PAUSE!!!! This is very IMPORTANT.
Use the following format:
Question: the input question you must answer
Thought: you should always think about what to do
Action: the action to take, should be one of [{", ".join(tool_map.keys())}]. This ONLY includes the name of the tool.
Action Input: the input to the action. This ONLY includes the parameters of the tool. Example: Action Input: a, "b", c
PAUSE
Observation: the result of the action
Thought: I now know the final answer
Final Answer: the final answer to the original input question
Begin!"""
def chat(client, messages) -> str:
completion = client.chat.completions.create(messages=messages, model="gpt-4o", stop="PAUSE")
completion_content = completion.choices[0].message.content
print("Chat completion:")
print(completion_content)
return completion_content
def parse_action(completion_content: str):
action_pattern = r"Action: ([^\n]*)"
match = re.search(action_pattern, completion_content)
if not match:
return None
tool_name = match.group(1)
if tool_name not in tool_map:
raise ValueError(f"Tool '{tool_name}' does not exist")
return tool_map[tool_name]
def parse_action_input(completion_content: str):
action_input_pattern = r"Action Input: ([^\n]*)"
match = re.search(action_input_pattern, completion_content)
if not match:
return None
args_string = match.group(1)
return eval(f"({args_string},)")
def main():
client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
question = "Convert 13:49 today in London time to Japan time. Please consider daylight saving."
print(f"Question: {question}")
messages = [{"role": "system", "content": system_prompt}, {"role": "user", "content": f"Question: {question}"}]
completion_content = chat(client, messages)
messages.append({"role": "assistant", "content": completion_content})
current_round = 0
max_round = 5
while "Final Answer" not in completion_content and current_round < max_round:
current_round += 1
action = parse_action(completion_content)
if action is None:
continue
action_input = parse_action_input(completion_content)
if action_input is None:
continue
action_result = action["function"](*action_input)
print(f"Action result: {action_result}")
messages.append({"role": "assistant", "content": f"Observation: {action_result}"})
completion_content = chat(client, messages)
messages.append({"role": "assistant", "content": completion_content})
if __name__ == '__main__':
main()
Top comments (0)