Call the API with Tools
On this page
Send Tools to the Mistral API
You have defined your functions and their JSON specifications. Now you need to send them to the Mistral API and, more importantly, understand what the model returns. We are now at steps 2 and 3 of the process: the model receives the user request along with the tools and produces a function call—or declines to make one, which is equally instructive.
The First Request
import os
import json
from mistralai.client import Mistral
client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])
# Tools defined in the previous lesson
tools = [
{
"type": "function",
"function": {
"name": "retrieve_payment_status",
"description": "Retrieves the status of a payment based on the transaction ID.",
"parameters": {
"type": "object",
"properties": {
"transaction_id": {
"type": "string",
"description": "Unique transaction identifier (e.g., T1001)"
}
},
"required": ["transaction_id"]
}
}
}
]
# User message
messages = [
{"role": "user", "content": "What is the status of payment T1001?"}
]
# API call with tools
response = client.chat.complete(
model="mistral-large-latest",
messages=messages,
tools=tools
)
Understanding the Model Response
The response contains a message object that can take two forms, and your code must handle both from day one. In the first case, the model responds directly because no function is needed: content holds text, and tool_calls is None.
# response.choices[0].message.content contains the text response
# response.choices[0].message.tool_calls is None
print(response.choices[0].message.content)
# → "Hello! How can I help you?"
In the second case, the model wants to call a function. The situation reverses: content is empty or null, and tool_calls carries the payload.
# response.choices[0].message.content is None or empty
# response.choices[0].message.tool_calls contains the calls
message = response.choices[0].message
if message.tool_calls:
for tool_call in message.tool_calls:
print(f"Function: {tool_call.function.name}")
print(f"Arguments: {tool_call.function.arguments}")
print(f"ID: {tool_call.id}")
Typical output:
Function: retrieve_payment_status
Arguments: {"transaction_id": "T1001"}
ID: call_abc123xyz
Parsing the Response and Extracting tool_calls
Here is the complete code to cleanly extract information from a tool call:
message = response.choices[0].message
if message.tool_calls and len(message.tool_calls) > 0:
tool_call = message.tool_calls[0]
# Extract the function name
function_name = tool_call.function.name
# Parse JSON arguments into a Python dictionary
function_params = json.loads(tool_call.function.arguments)
# Retrieve the unique call identifier
tool_call_id = tool_call.id
print(f"The model wants to call: {function_name}")
print(f"With parameters: {function_params}")
print(f"Call ID: {tool_call_id}")
else:
# The model responded directly
print(f"Response: {message.content}")
Three pieces of information come out of this block, and the third is the one people forget. The function name is used for dispatching, the parsed arguments are used for the call, but the tool_call_id is what allows the model to associate your result with the correct call when you send it back. Lose it, and the conversation turn becomes unrecoverable.
When the Model Requests Missing Information
Sometimes the user does not provide enough information. The model has an elegant way out: do not call a function at all, and ask a clarifying question instead.
messages = [
{"role": "user", "content": "What is the status of my payment?"}
]
response = client.chat.complete(
model="mistral-large-latest",
messages=messages,
tools=tools
)
message = response.choices[0].message
print(message.content)
# → "I need the transaction ID to check the status.
# Could you please provide the transaction number?"
This behavior is exactly what you want. The model knows that transaction_id is listed in required; rather than inventing a plausible ID and triggering a request on non-existent data, it yields the floor to the user. This is also the best demonstration of the utility of the required field covered in the previous lesson.
Handling the System Message
The system message lets you frame this behavior rather than hoping for it.
messages = [
{
"role": "system",
"content": "You are a payment tracking assistant. "
"Respond in English. "
"Use the available tools to answer questions "
"about transactions. If the user does not provide "
"the transaction ID, ask for it politely."
},
{
"role": "user",
"content": "What is the status of payment T1001?"
}
]
Read it line by line: the first sentence sets the role, the second sets the tone and language of the assistant, the third specifies when to use functions, and the last gives an explicit instruction for an edge case—missing information. These are the three use cases for which a system message is recommended, and they suffice most of the time.
Complete Code for Steps 2–3
Here is the complete, reusable code for sending a request with tools. It encapsulates everything above and normalizes the response into a dictionary with two forms, saving you from scattering if message.tool_calls checks throughout your application.
import os
import json
from mistralai.client import Mistral
def call_mistral_with_tools(
messages: list,
tools: list,
model: str = "mistral-large-latest",
tool_choice: str = "auto"
) -> dict:
"""Sends a request to Mistral with tools and returns the parsed response."""
client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])
response = client.chat.complete(
model=model,
messages=messages,
tools=tools,
tool_choice=tool_choice
)
message = response.choices[0].message
if message.tool_calls and len(message.tool_calls) > 0:
return {
"type": "tool_call",
"message": message,
"tool_calls": [
{
"id": tc.id,
"function_name": tc.function.name,
"arguments": json.loads(tc.function.arguments)
}
for tc in message.tool_calls
]
}
return {
"type": "text",
"message": message,
"content": message.content
}
Note that the list comprehension iterates over all tool_calls, not just the first one. The model can produce several at once; a later lesson will cover that, but your utility function is already ready to handle them.
Key Takeaways
- Send
toolsandmessagestoclient.chat.complete()to enable function calling - Always check if
message.tool_callsexists before attempting to parse - The
tool_call_idis mandatory for sending the result back to the model - Arguments are a JSON string that must be parsed with
json.loads() - The model may choose to respond directly if it does not need a function or if information is missing