In a previous post, we looked into how to make function calls in Javascript using the Gemini models. Since we haven’t covered Anthropic yet on this blog, and they’ve recently released the Claude 3.5 Sonnet model, let’s take the opportunity to test it out by doing something similar to that post, but using Python.
Why an how to call functions
Function calling (also called tool usage) allow language models to overcome two significant limitations:
- their knowledge is confined to their training data or the information we directly provide them
- they cannot perform actions or interact with anything external to themselves.
Function calls enable models to address both of these shortcomings, giving us the opportunity to equip them with tools that grant access to information not available during their training (such as weather conditions, flight statuses, stock quotes, etc.) or take actions (book flights, buy stock, create calendar entries).
The function-calling capability (despite its name) doesn’t enable the model to directly call services; what the model will do is indicate that a function call should be made, so our application can perform it. Detailing the interaction in more detail, the flow would be:
- The application starts the interaction with the language model. While the model doesn’t need a function call, the message exchange between the user and the model proceeds normally.
- When the model determines that a function call is required, it will send a message in a specific format, specifying which function to execute and with what parameters.
- The application detects that the received message is a function call and executes the appropriate function.
- The application sends a “function response” message back to the model with the function’s response.
- The model, now having the necessary information from the function, resumes the message exchange, benefiting from the additional information obtained from the function.

Olivia
To illustrate the use of function calls, let’s build Olivia, our personal kitchen assistant. Olivia will help us decide our menu and find new recipes. Additionally, she will serve as a recipe archivist, being able to save recipes and consult the ones we already have stored.
We will implement our functions save_recipe and list_recipes using SQLite, which allows us to persist the recipe database in a file easily. Most Python installations already include SQLite as a standard library, but if that is not the case, we will need to install it, along with the Anthropic library for Python.
pip install anthropic
pip install sqlite
Function specifications
To use the functions, Claude needs us to specify them first. It’s important that the specifications are as clear and precise as possible in terms of description and input parameters to prevent the model from attempting to use them out of place or generating calls with incorrect parameters. In our case, we will specify our two functions –save_recipe and list_recipes– as follows:
save_recipe_declaration = {
"name": "save_recipe",
"description": "Save a new recipe",
"input_schema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Name of the recipe.",
},
"ingredients": {
"type": "string",
"description": "List of ingredients needed to prepare the recipe, with their quantities.",
},
"instructions": {
"type": "string",
"description": "List of specific steps required to prepare the recipe, including order, cooking times, and any details that need to be specified so that a person with no prior knowledge can successfully make it.",
},
},
"required": ["name", "ingredients", "instructions"],
}
}
list_recipes_declaration = {
"name": "list_recipes",
"description": "List saved recipes.",
"input_schema": {
"type": "object",
"properties": {}
}
}
Implementation of functions
The implementations for both services are:
table_exists = False
def list_recipes():
# Gets the list of stored recipes
conn = sqlite3.connect("recipes.db")
try:
recipes = [{"name":row[0], "ingredients":row[1], "instructions":row[2]} for row in conn.execute("select name, ingredients, instructions from recipe")]
except:
recipes = []
conn.close()
return recipes
def save_recipe(name, ingredients, instructions):
# Save a new recipe
conn = sqlite3.connect("recipes.db")
cur = conn.cursor()
if not table_exists:
cur.execute("""CREATE TABLE if not exists recipe (
[name] TEXT NOT NULL,
[ingredients] TEXT,
[instructions] TEXT)""")
cur.execute("insert into recipe (name, ingredients, instructions) values (?,?,?)",
(name, ingredients, instructions))
conn.commit()
cur.close()
conn.close()
System prompt
To make Olivia behave as we want, it’s important to provide her with a detailed system prompt outlining our expectations. Every call we make to Claude will include this message in the system field. The prompt we will use will be:
# WHO YOU ARE
You are Olivia, a great chef and recipe archivist. With your encyclopedic knowledge, you are capable of proposing recipes, rewriting them to the user's liking, and modifying them to suit their preferences.
# BEHAVIOR
Talk with the user, find out their tastes, advise them on possible recipes, and modify them according to their requests. Once the user confirms they are satisfied with the recipe, ask if they would like to save it, and if so, generate a call to the "save_recipe" service.
If the user wants to know the recipes they already have saved, you must retrieve the saved recipes by calling the "list_recipes" service. This service will return a list of objects, each representing a recipe, with the structure explained below.
# RECIPE STRUCTURE
A recipe consists of three parts:
- Title
- Ingredients: a list of ingredients needed to prepare the recipe, with their quantities.
- Instructions: a list of specific steps required to prepare the recipe, including order, cooking times, and any details that need to be specified so that a person with no prior knowledge can successfully make it.
We will store the system prompt in a separate text file, which we will read at the beginning to use during the conversation.
Interaction with Claude
All interaction with the language model will be encapsulated within a class called ClaudeClient:
class ClaudeClient:
def __init__(self, model, system_prompt):
self.model = model
self.system_prompt = system_prompt
self.anthropic = Anthropic()
self.messages = []
def __call_function(self, llf):
if llf.name == "list_recipes":
return json.dumps(list_recipes())
elif llf.name == "save_recipe":
return json.dumps(save_recipe(llf.input["name"], llf.input["ingredients"], llf.input["instructions"]))
def chat(self, user_message):
self.messages.append(
{
"role": "user",
"content": user_message
}
)
response = self.anthropic.messages.create(
max_tokens=4096,
system=self.system_prompt,
messages=self.messages,
model = self.model,
tools=[save_recipe_declaration, list_recipes_declaration]
)
self.messages.append(
{
"role": "assistant",
"content": response.content
}
)
if response.stop_reason == "tool_use":
output = ""
function_result = []
for c in response.content:
if c.type == "text":
output = c.text
elif c.type == "tool_use":
function_result.append(
{
"type": "tool_result",
"tool_use_id": c.id,
"content": self.__call_function(c)
})
self.messages.append(
{
"role": "user",
"content": function_result
})
response2 = response = self.anthropic.messages.create(
max_tokens=4096,
system=self.system_prompt,
messages=self.messages,
model = self.model,
tools=[save_recipe_declaration, list_recipes_declaration]
)
self.messages.append(
{
"role": "assistant",
"content": response2.content
}
)
return output+"\n"+response2.content[0].text
return response.content[0].text
The core of the code is in the chat method, which is responsible for obtaining and returning the response to the user’s message:
- It starts by calling the messages.create service from the Claude library, passing the complete list of conversation messages, the system prompt, and the declarations of the services that we want to enable (provided as a list in the tools parameter). If the model’s response does not require executing any function, it will save the response and return it to be displayed on the screen, allowing the conversation to continue.
- When Claude requests to call a service, he will notify us by indicating in the response the stop_reason parameter with the value tool_use. In this case, the content parameter list will contain at least one tool_use element; each of these elements (there can be more than one) represents a function call that our application must execute.
- The next message the application sends to Claude must contain a tool_result element for each function call made. To properly distinguish the calls and responses, each tool_use element includes an id parameter whose value must be reported in the corresponding tool_result.
- For simplicity, this code does not consider the possibility of Claude requesting a function call immediately after another function call, but it’s something that could theoretically occur.
Chat interface
Finally, we just need the main body of the script, which will read the system message from a file, instantiate the ClaudeClient class, and display the chat interface in a loop:
from anthropic import Anthropic
import sqlite3
import io
import json
with io.open("olivia.md", "r") as arch:
system_prompt = "\n".join(arch.readlines())
client = ClaudeClient("claude-3-5-sonnet-20240620", system_prompt)
while (user_text := input("What do you want to say to Olivia?\n> ").strip()) !="":
if len(user_text)==0:
break
print(client.chat(user_text))
And with that, we have Olivia finished! I hope it proves useful, but above all, that this example has made it clear how to use function call functionality, which can greatly expand what we can do with our applications based on large language models.
As usual, the complete code can be found on GitHub.
